From 42b721cff542c729b3b63ca137a4282d77297db8 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 11 May 2026 11:25:41 +0200 Subject: [PATCH 01/32] refactor(ml): remove intermediate step with picking data source for Data Drift and AIOps pages --- .../data_drift/data_drift_app_state.tsx | 7 +- .../data_drift/data_drift_page.tsx | 86 ++++---- .../translations/translations/de-DE.json | 3 - .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../change_point_detection_root.tsx | 40 +++- .../log_categorization_app_state.tsx | 29 ++- .../log_categorization_page.tsx | 9 +- .../log_rate_analysis_app_state.tsx | 33 ++- .../log_rate_analysis_page.tsx | 8 +- .../components/page_header/page_header.tsx | 38 +++- .../aiops/change_point_detection.tsx | 97 +++++---- .../application/aiops/log_categorization.tsx | 84 +++---- .../application/aiops/log_rate_analysis.tsx | 87 ++++---- .../data_source_picker/data_source_picker.tsx | 205 ++++++++++++++++++ .../components/ml_page/side_nav.tsx | 6 +- .../data_drift/data_drift_page.tsx | 53 ++++- .../data_drift/index_patterns_picker.tsx | 103 +-------- .../public/application/routing/breadcrumbs.ts | 14 +- .../routing/routes/aiops/index_or_search.tsx | 148 ++----------- .../routes/datavisualizer/data_comparison.tsx | 17 +- .../routes/datavisualizer/data_drift.tsx | 72 +----- .../shared/ml/public/locator/ml_locator.ts | 13 +- .../search_deep_links.ts | 11 +- 25 files changed, 621 insertions(+), 551 deletions(-) create mode 100644 x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_app_state.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_app_state.tsx index e1d525a0250d7..e240a16377f4b 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_app_state.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_app_state.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React from 'react'; import { pick } from 'lodash'; @@ -40,6 +40,8 @@ export interface DataDriftDetectionAppStateProps { dataView: DataView; /** The saved search to analyze. */ savedSearch: SavedSearch | null; + /** Optional content rendered in the page header in place of the data view name */ + headerContent?: ReactNode; } export type DataDriftSpec = typeof DataDriftDetectionAppState; @@ -57,6 +59,7 @@ const getStr = (arg: string | string[] | null, fallbackStr?: string): string => export const DataDriftDetectionAppState: FC = ({ dataView, savedSearch, + headerContent, }) => { if (!(dataView || savedSearch)) { throw Error('No data view or saved search available.'); @@ -147,7 +150,7 @@ export const DataDriftDetectionAppState: FC = ( comparison: comparisonStateManager, }} > - + diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx index da4205cefa30c..c612b2f684ef5 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; import type { estypes } from '@elastic/elasticsearch'; @@ -16,7 +16,6 @@ import { EuiPageSection, EuiPanel, EuiSpacer, - EuiPageHeader, EuiHorizontalRule, EuiBadge, } from '@elastic/eui'; @@ -35,7 +34,6 @@ import { useTimefilter, } from '@kbn/ml-date-picker'; import moment from 'moment'; -import { css } from '@emotion/react'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; @@ -57,15 +55,12 @@ import { useSearch } from '../common/hooks/use_search'; import { DocumentCountWithBrush } from './document_count_with_brush'; import { useDataDriftColors } from './use_data_drift_colors'; -const dataViewTitleHeader = css({ - minWidth: '300px', -}); - interface PageHeaderProps { onRefresh: () => void; needsUpdate: boolean; + headerContent?: ReactNode; } -export const PageHeader: FC = ({ onRefresh, needsUpdate }) => { +export const PageHeader: FC = ({ onRefresh, needsUpdate, headerContent }) => { const [, setGlobalState] = useUrlState('_g'); const { dataView } = useDataSource(); @@ -101,37 +96,41 @@ export const PageHeader: FC = ({ onRefresh, needsUpdate }) => { ); return ( - - {dataView.getName()} - - } - rightSideGroupProps={{ - gutterSize: 's', - 'data-test-subj': 'dataComparisonTimeRangeSelectorSection', - }} - rightSideItems={[ - , - hasValidTimeField && ( - - ), - ].filter(Boolean)} - /> + + {headerContent ?? null} + + + {hasValidTimeField && ( + + + + )} + + + + + + ); }; @@ -147,12 +146,13 @@ const getDataDriftDataLabel = (label: string, indexPattern?: string) => ( ); interface Props { initialSettings: InitialSettings; + headerContent?: ReactNode; } const isBarBetween = (start: number, end: number, min: number, max: number) => { return start >= min && end <= max; }; -export const DataDriftPage: FC = ({ initialSettings }) => { +export const DataDriftPage: FC = ({ initialSettings, headerContent }) => { const { services: { data: dataService, uiSettings, cps }, } = useDataVisualizerKibana(); @@ -404,7 +404,11 @@ export const DataDriftPage: FC = ({ initialSettings }) => { return ( - + diff --git a/x-pack/platform/plugins/private/translations/translations/de-DE.json b/x-pack/platform/plugins/private/translations/translations/de-DE.json index d27655a0f7b0b..02a62d216957f 100644 --- a/x-pack/platform/plugins/private/translations/translations/de-DE.json +++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json @@ -29275,7 +29275,6 @@ "xpack.ml.aiopsBreadcrumbLabel": "AIOps Labs", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "Änderungspunkterkennung", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "Analyse der Log-Rate", - "xpack.ml.aiopsBreadcrumbs.selectDataViewLabel": "Datenansicht auswählen", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "Das Prüfintervall ist größer als das Lookback-Intervall. Reduzieren Sie es auf {lookbackInterval}, um möglicherweise fehlende Benachrichtigungen zu vermeiden.", "xpack.ml.alertConditionValidation.notifyWhenWarning": "Erwarten Sie, bis zu {notificationDuration, plural, one {# minute} other {# minutes}} doppelte Benachrichtigungen über dieselbe Anomalie zu erhalten. Erhöhen Sie das Überprüfungsintervall oder wechseln Sie zu Benachrichtigung nur bei Statusänderung, um doppelte Benachrichtigungen zu vermeiden.", "xpack.ml.alertConditionValidation.stoppedDatafeedJobsMessage": "Der Datenfeed wurde nicht gestartet für die folgenden {count, plural, one {job} other {Jobs}}: {jobIds}.", @@ -29768,7 +29767,6 @@ "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "Zeitstempel-Feld", "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "Wählen Sie ein optionales Zeitstempelfeld aus", "xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "Zeitstempel-Feld", - "xpack.ml.dataDruiftWithDocCount.pageHeader": "Datendrift", "xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "Dokumente zur Klassifizierungsbewertung", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "Eigentliche Klasse", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "Normalisierte Konfusionsmatrix für den gesamten Datensatz", @@ -32574,7 +32572,6 @@ "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "Geben Sie eine Phrase zum Testen ein", "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "Zero-Shot-Klassifizierung", "xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "Datendrift", - "xpack.ml.trainedModelsBreadcrumbs.dataDriftResultsLabel": "Ergebnisse", "xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "Trainierte Modelle", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "Indizes im Zusammenhang mit Machine Learning werden derzeit aktualisiert.", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "Einige Aktionen werden während dieser Zeit nicht verfügbar sein.", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 57395c72470ff..adf569b39425d 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -29228,7 +29228,6 @@ "xpack.ml.aiopsBreadcrumbLabel": "AIOps Labs", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "Modifier la détection du point", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "Analyse du taux de log", - "xpack.ml.aiopsBreadcrumbs.selectDataViewLabel": "Sélectionner la vue de données", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "L'intervalle de vérification est supérieur à l'intervalle d'historique. Réduisez-le à {lookbackInterval} pour éviter des notifications potentiellement manquantes.", "xpack.ml.alertConditionValidation.notifyWhenWarning": "Attendez-vous à recevoir des notifications en double au sujet d'une même anomalie pendant une durée pouvant aller jusqu'à {notificationDuration, plural, one {# minute} other {# minutes}}. Augmentez l'intervalle de vérification ou passez aux notifications Seulement lors d'un changement de statut pour éviter de recevoir des notifications en double.", "xpack.ml.alertConditionValidation.title": "La condition d'alerte contient les problèmes suivants :", @@ -29719,7 +29718,6 @@ "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "Champ d'horodatage", "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "Sélectionner un champ d'horodatage différent", "xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "Champ d'horodatage", - "xpack.ml.dataDruiftWithDocCount.pageHeader": "Dérive de données", "xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "Documents d'évaluation de classification", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "Classe réelle", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "Matrice de confusion normalisée pour l'ensemble de données entier", @@ -32510,7 +32508,6 @@ "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "Entrer une expression à tester", "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "Classification Zero-Shot", "xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "Dérive de données", - "xpack.ml.trainedModelsBreadcrumbs.dataDriftResultsLabel": "Résultats", "xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "Modèles entraînés", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "Les index associés au Machine Learning sont actuellement en cours de mise à niveau.", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "Certaines actions ne seront pas disponibles pendant cette opération.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 5ac425ff027fb..b6bcbbe958f25 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -29375,7 +29375,6 @@ "xpack.ml.aiopsBreadcrumbLabel": "AIOps Labs", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "変化点検出", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "ログレート分析", - "xpack.ml.aiopsBreadcrumbs.selectDataViewLabel": "データビューを選択", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "チェック間隔がルックバック間隔を超えています。通知を見逃す可能性を回避するには、{lookbackInterval}に減らします。", "xpack.ml.alertConditionValidation.notifyWhenWarning": "最大{notificationDuration, plural, one {# minute} other {#分間}}は同じ異常に関する重複した通知を受信することが想定されます。重複した通知を回避するには、チェック間隔を大きくするか、ステータス変更時のみに通知するように切り替えます。", "xpack.ml.alertConditionValidation.stoppedDatafeedJobsMessage": "次の{count, plural, one {job} other {ジョブ}} のデータフィードが開始していません:{jobIds}.", @@ -29870,7 +29869,6 @@ "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "タイムスタンプフィールド", "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "任意のタイムスタンプフィールドを選択", "xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "タイムスタンプフィールド", - "xpack.ml.dataDruiftWithDocCount.pageHeader": "データドリフト", "xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "分類評価ドキュメント", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "実際のクラス", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "データセット全体で正規化された混同行列", @@ -32688,7 +32686,6 @@ "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "テストするフレーズを入力", "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "ゼロショット分類", "xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "データドリフト", - "xpack.ml.trainedModelsBreadcrumbs.dataDriftResultsLabel": "結果", "xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "学習済みモデル", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "機械学習に関連したインデックスは現在アップグレード中です。", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "現在いくつかのアクションが利用できません。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index ff8b476107954..4311976f3c223 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -29379,7 +29379,6 @@ "xpack.ml.aiopsBreadcrumbLabel": "AIOps 实验室", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "更改点检测", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "日志速率分析", - "xpack.ml.aiopsBreadcrumbs.selectDataViewLabel": "选择数据视图", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "检查时间间隔大于回溯时间间隔。将其减少为 {lookbackInterval} 以避免通知可能丢失。", "xpack.ml.alertConditionValidation.notifyWhenWarning": "预计会收到有关同一异常的重复通知,最长可达 {notificationDuration, plural, one {# minute} other {# 分钟}}。增大检查时间间隔或切换到仅在状态更改时通知,以避免重复通知。", "xpack.ml.alertConditionValidation.stoppedDatafeedJobsMessage": "未为以下 {count, plural, one {作业} other {作业}} 启动数据馈送:{jobIds}。", @@ -29875,7 +29874,6 @@ "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "时间戳字段", "xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "选择可选的时间戳字段", "xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "时间戳字段", - "xpack.ml.dataDruiftWithDocCount.pageHeader": "数据偏移", "xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "分类评估文档", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "实际类", "xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "整个数据集的标准化混淆矩阵", @@ -32690,7 +32688,6 @@ "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "输入短语以进行测试", "xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "Zero shot 分类", "xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "数据偏移", - "xpack.ml.trainedModelsBreadcrumbs.dataDriftResultsLabel": "结果", "xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "已训练模型", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "当前正在升级与 Machine Learning 相关的索引。", "xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "此次某些操作不可用。", diff --git a/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx b/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx index 4a2bfa87eff38..ad90416a79ad6 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useMemo } from 'react'; import type { Observable } from 'rxjs'; import { EMPTY, map, merge } from 'rxjs'; @@ -48,13 +48,23 @@ const localStorage = new Storage(window.localStorage); */ export interface ChangePointDetectionAppStateProps { /** The data view to analyze. */ - dataView: DataView; + dataView: DataView | undefined; /** The saved search to analyze. */ savedSearch: SavedSearch | null; /** App context value */ appContextValue: AiopsAppContextValue; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; + /** Optional page title for screen readers */ + pageTitle?: ReactNode; + /** + * Optional data source picker rendered in the page header. When provided it + * replaces the static data view title. Typically a `DataDriftDataSourcePicker`- + * style component supplied by the host application. + */ + headerContent?: ReactNode; + /** Optional content rendered to the right of the header content */ + rightSideItems?: ReactNode; } export const ChangePointDetectionAppState: FC = ({ @@ -62,6 +72,9 @@ export const ChangePointDetectionAppState: FC savedSearch, appContextValue, showFrozenDataTierChoice = true, + pageTitle, + headerContent, + rightSideItems, }) => { const datePickerDeps: DatePickerDependencies = { ...pick(appContextValue, [ @@ -77,8 +90,6 @@ export const ChangePointDetectionAppState: FC showFrozenDataTierChoice, }; - const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); - const reload$ = useMemo>( () => merge( @@ -88,6 +99,19 @@ export const ChangePointDetectionAppState: FC [appContextValue.cps?.cpsManager] ); + if (!dataView) { + if (headerContent !== undefined) { + return ( + + {headerContent} + + ); + } + return null; + } + + const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); + if (warning !== null) { return <>{warning}; } @@ -101,10 +125,14 @@ export const ChangePointDetectionAppState: FC - + - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx index 42111480f9994..5ed0284003437 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React from 'react'; import { pick } from 'lodash'; @@ -31,13 +31,21 @@ const localStorage = new Storage(window.localStorage); */ export interface LogCategorizationAppStateProps { /** The data view to analyze. */ - dataView: DataView; + dataView: DataView | undefined; /** The saved search to analyze. */ savedSearch: SavedSearch | null; /** App context value */ appContextValue: AiopsAppContextValue; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; + /** Optional page title for screen readers */ + pageTitle?: ReactNode; + /** + * Optional data source picker rendered in the page header. When provided it + * replaces the static data view title. Typically a `DataDriftDataSourcePicker`- + * style component supplied by the host application. + */ + headerContent?: ReactNode; } export const LogCategorizationAppState: FC = ({ @@ -45,8 +53,19 @@ export const LogCategorizationAppState: FC = ({ savedSearch, appContextValue, showFrozenDataTierChoice = true, + pageTitle, + headerContent, }) => { - if (!dataView) return null; + if (!dataView) { + if (headerContent !== undefined) { + return ( + + {headerContent} + + ); + } + return null; + } const warning = timeSeriesDataViewWarning(dataView, 'log_categorization'); @@ -76,10 +95,10 @@ export const LogCategorizationAppState: FC = ({ - + - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx index bf6e0cc1b64d1..44e56cf42e157 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react'; import type { estypes } from '@elastic/elasticsearch'; @@ -60,7 +60,10 @@ import { AttachmentsMenu } from './attachments_menu'; const BAR_TARGET = 20; const DEFAULT_SELECTED_FIELD = 'message'; -export const LogCategorizationPage: FC = () => { +export const LogCategorizationPage: FC<{ pageTitle?: ReactNode; headerContent?: ReactNode }> = ({ + pageTitle, + headerContent, +}) => { const { notifications: { toasts }, embeddingOrigin, @@ -349,7 +352,7 @@ export const LogCategorizationPage: FC = () => { return ( - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index 9cebaf4a3c766..c299c230a6b00 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React from 'react'; import { pick } from 'lodash'; @@ -34,7 +34,7 @@ const localStorage = new Storage(window.localStorage); */ export interface LogRateAnalysisAppStateProps { /** The data view to analyze. */ - dataView: DataView; + dataView: DataView | undefined; /** The saved search to analyze. */ savedSearch: SavedSearch | null; /** App context value */ @@ -43,6 +43,14 @@ export interface LogRateAnalysisAppStateProps { showContextualInsights?: boolean; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; + /** Optional page title for screen readers */ + pageTitle?: ReactNode; + /** + * Optional data source picker rendered in the page header. When provided it + * replaces the static data view title. Typically a `DataDriftDataSourcePicker`- + * style component supplied by the host application. + */ + headerContent?: ReactNode; } export const LogRateAnalysisAppState: FC = ({ @@ -51,8 +59,19 @@ export const LogRateAnalysisAppState: FC = ({ appContextValue, showContextualInsights = false, showFrozenDataTierChoice = true, + pageTitle, + headerContent, }) => { - if (!dataView) return null; + if (!dataView) { + if (headerContent !== undefined) { + return ( + + {headerContent} + + ); + } + return null; + } const warning = timeSeriesDataViewWarning(dataView, 'log_rate_analysis'); @@ -80,12 +99,16 @@ export const LogRateAnalysisAppState: FC = ({ - + - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx index 5d6be2820f924..90ab3eb1d747a 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { isEqual, orderBy } from 'lodash'; @@ -58,10 +58,14 @@ interface SignificantFieldValue { interface LogRateAnalysisPageProps { showContextualInsights?: boolean; + pageTitle?: ReactNode; + headerContent?: ReactNode; } export const LogRateAnalysisPage: FC = ({ showContextualInsights = false, + pageTitle, + headerContent, }) => { const aiopsAppContext = useAiopsAppContext(); const { data: dataService, observabilityAIAssistant } = aiopsAppContext; @@ -296,7 +300,7 @@ export const LogRateAnalysisPage: FC = ({ return ( - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx b/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx index b98d161d9a925..96420daef28f5 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx @@ -6,10 +6,10 @@ */ import { css } from '@emotion/react'; -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useCallback, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiScreenReaderOnly, EuiTitle } from '@elastic/eui'; import { useUrlState } from '@kbn/ml-url-state'; import { useStorage } from '@kbn/ml-local-storage'; @@ -34,7 +34,19 @@ const maxInlineSizeStyles = css` min-inline-size: 0; `; -export const PageHeader: FC = () => { +export interface PageHeaderProps { + /** Screen-reader page title rendered as an h1 when `headerContent` is provided. */ + pageTitle?: ReactNode; + /** Optional content rendered to the right of the header content */ + rightSideItems?: ReactNode; + /** + * When provided, rendered on the left side of the header in place of the + * static data view title. Typically the data source picker component. + */ + headerContent?: ReactNode; +} + +export const PageHeader: FC = ({ pageTitle, rightSideItems, headerContent }) => { const [, setGlobalState] = useUrlState('_g'); const { dataView } = useDataSource(); @@ -72,9 +84,23 @@ export const PageHeader: FC = () => { return ( - -

{dataView.getName()}

-
+ {headerContent !== undefined ? ( + <> + {pageTitle ? ( + +

{pageTitle}

+
+ ) : null} + + {headerContent} + {rightSideItems ? {rightSideItems} : null} + + + ) : ( + +

{dataView.getName()}

+
+ )}
{ const { services } = useMlKibana(); @@ -31,59 +31,62 @@ export const ChangePointDetectionPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const pageTitle = ( + + ); + + const headerContent = ( + + ); + return ( <> - + - - } - /> + - {dataView ? ( - - ) : null} + { const { services } = useMlKibana(); @@ -26,50 +27,53 @@ export const LogCategorizationPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const pageTitle = ( + + ); + + const headerContent = ( + + ); + return ( <> - - } - /> + - {dataView && ( - - )} + ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index 1cff7e61cca4c..cfe212c4581a7 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -16,9 +16,10 @@ import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import { useDataSource } from '../contexts/ml/data_source_context'; import { useMlKibana } from '../contexts/kibana'; import { HelpMenu } from '../components/help_menu'; -import { MlPageHeader } from '../components/page_header'; import { useEnabledFeatures } from '../contexts/ml'; +import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; +import { DataSourcePicker } from '../components/data_source_picker/data_source_picker'; export const LogRateAnalysisPage: FC = () => { const { services } = useMlKibana(); @@ -26,52 +27,52 @@ export const LogRateAnalysisPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const pageTitle = ( + + ); + + const headerContent = ( + + ); + return ( <> - - } - /> + - {dataView && ( - - )} + ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx new file mode 100644 index 0000000000000..4f7dacb16eb0d --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx @@ -0,0 +1,205 @@ +/* + * 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, { useCallback, useEffect, useRef, useState } from 'react'; +import { css } from '@emotion/react'; +import { useLocation } from 'react-router-dom'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFormControlButton, + EuiFormControlLayout, + EuiPopover, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; + +import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; + +type SavedObject = SavedObjectCommon; + +const pickerPanelCss = css({ width: 600, maxHeight: '70vh', overflow: 'auto' }); + +export interface DataSourcePickerProps { + currentDataView: DataView | null; + currentSavedSearch: SavedSearch | null; + /** + * Called when the user clicks "Create a data view". When omitted, the standard + * data view editor flyout is opened and the page navigates to the newly created view. + */ + onCreateDataView?: () => void; + /** data-test-subj for the "Create a data view" button */ + createDataViewButtonTestSubj?: string; +} + +export const DataSourcePicker: FC = ({ + currentDataView, + currentSavedSearch, + onCreateDataView, + createDataViewButtonTestSubj = 'mlDataSourcePickerCreateDataViewButton', +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const navigateToPath = useNavigateToPath(); + const location = useLocation(); + const { contentManagement, uiSettings, dataViewEditor } = useMlKibana().services; + const closeDataViewEditorRef = useRef<() => void | undefined>(); + + const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + + const onChoose = (id: string, type: string) => { + setIsPopoverOpen(false); + const param = type === 'index-pattern' ? 'index' : 'savedSearchId'; + navigateToPath(`${location.pathname}?${param}=${encodeURIComponent(id)}`); + }; + + const openCreateDataViewFlyout = useCallback(() => { + setIsPopoverOpen(false); + closeDataViewEditorRef.current = dataViewEditor?.openEditor({ + onSave: (dataView) => { + if (dataView.id) { + navigateToPath(`${location.pathname}?index=${encodeURIComponent(dataView.id)}`); + } + }, + }); + }, [dataViewEditor, navigateToPath, location.pathname]); + + useEffect(function cleanUpFlyout() { + return () => { + if (closeDataViewEditorRef.current) { + closeDataViewEditorRef.current(); + } + }; + }, []); + + const handleCreateDataView = useCallback(() => { + setIsPopoverOpen(false); + if (onCreateDataView) { + onCreateDataView(); + } else { + openCreateDataViewFlyout(); + } + }, [onCreateDataView, openCreateDataViewFlyout]); + + const triggerLabel = currentSavedSearch?.title + ? currentSavedSearch.title + : currentDataView?.getName() ?? ''; + + const prepend = currentSavedSearch?.title + ? i18n.translate('xpack.ml.dataSourcePicker.discoverSessionLabel', { + defaultMessage: 'Discover session', + }) + : currentDataView + ? i18n.translate('xpack.ml.dataSourcePicker.dataViewLabel', { + defaultMessage: 'Data view', + }) + : undefined; + + const triggerButton = ( + setIsPopoverOpen(!isPopoverOpen)} + > + + + {triggerLabel || + i18n.translate('xpack.ml.dataSourcePicker.placeholderLabel', { + defaultMessage: 'Select data source', + })} + + + + ); + + const popover = ( + setIsPopoverOpen(false)} + panelPaddingSize="none" + panelProps={{ css: pickerPanelCss }} + aria-label={i18n.translate('xpack.ml.dataSourcePicker.popoverAriaLabel', { + defaultMessage: 'Data source selector', + })} + > + 'discoverApp', + name: i18n.translate('xpack.ml.dataSourcePicker.savedObjectType.discoverSession', { + defaultMessage: 'Discover session', + }), + showSavedObject: (savedObject: SavedObject) => + savedObject.attributes.isTextBasedQuery !== true, + }, + { + type: 'index-pattern', + getIconForSavedObject: () => 'indexPatternApp', + name: i18n.translate('xpack.ml.dataSourcePicker.savedObjectType.dataView', { + defaultMessage: 'Data view', + }), + }, + ]} + fixedPageSize={20} + services={{ + contentClient: contentManagement.client, + uiSettings, + }} + > + + + + + + ); + + if (prepend) { + return ( + + + + {popover} + + + + ); + } + + return popover; +}; diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_page/side_nav.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/ml_page/side_nav.tsx index b2387ad38634e..cd7f38ffc6987 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/ml_page/side_nav.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/ml_page/side_nav.tsx @@ -192,7 +192,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { items: [ { id: 'logRateAnalysis', - pathId: ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT, + pathId: ML_PAGES.AIOPS_LOG_RATE_ANALYSIS, name: i18n.translate('xpack.ml.navMenu.logRateAnalysisLinkText', { defaultMessage: 'Log rate analysis', }), @@ -202,7 +202,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { }, { id: 'logCategorization', - pathId: ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT, + pathId: ML_PAGES.AIOPS_LOG_CATEGORIZATION, name: i18n.translate('xpack.ml.navMenu.logCategorizationLinkText', { defaultMessage: 'Log pattern analysis', }), @@ -214,7 +214,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { ? [ { id: 'chartChangePoint', - pathId: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT, + pathId: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, name: i18n.translate('xpack.ml.navMenu.changePointDetectionLinkText', { defaultMessage: 'Change point detection', }), diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx index 28393f02da6ba..b32208b10e45c 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx @@ -7,12 +7,17 @@ import type { FC } from 'react'; import React, { useEffect, useState } from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DataDriftSpec } from '@kbn/data-visualizer-plugin/public'; -import { useMlKibana } from '../../contexts/kibana'; + +import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; +import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; import { useDataSource } from '../../contexts/ml'; import { MlPageHeader } from '../../components/page_header'; import { PageTitle } from '../../components/page_title'; +import { DataSourcePicker } from '../../components/data_source_picker/data_source_picker'; +import { createPath } from '../../routing/router'; export const DataDriftPage: FC = () => { const { @@ -29,22 +34,56 @@ export const DataDriftPage: FC = () => { }, [dataVisualizer]); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const navigateToPath = useNavigateToPath(); + + const dataSourcePicker = ( + navigateToPath(createPath(ML_PAGES.DATA_DRIFT_CUSTOM))} + createDataViewButtonTestSubj="dataDriftCreateDataViewButton" + /> + ); return ( <> + } /> {dataView && DataDriftView ? ( - - ) : null} + + ) : ( + <> + {dataSourcePicker} + + + + } + body={ +

+ +

+ } + /> + + )} ); }; diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx index bfe1c34ed57a9..782bc7afc261e 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx @@ -7,116 +7,17 @@ import type { FC } from 'react'; import React, { useEffect, useState, useMemo } from 'react'; -import { EuiPageBody, EuiPageSection, EuiButton, EuiPanel } from '@elastic/eui'; +import { EuiPageBody, EuiPageSection } from '@elastic/eui'; import { parse } from 'query-string'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { type DataViewEditorService as DataViewEditorServiceSpec } from '@kbn/data-view-editor-plugin/public'; import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public'; -import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; -import { isEsqlSavedSearch, type DiscoverSessionFinderAttributes } from '@kbn/discover-utils'; -import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; -import { createPath } from '../../routing/router'; import { DataDriftIndexPatternsEditor } from './data_drift_index_patterns_editor'; import { MlPageHeader } from '../../components/page_header'; -import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; +import { useMlKibana } from '../../contexts/kibana'; import { PageTitle } from '../../components/page_title'; -type SavedObject = SavedObjectCommon; - -export const DataDriftIndexOrSearchRedirect: FC = () => { - const navigateToPath = useNavigateToPath(); - const { contentManagement, uiSettings } = useMlKibana().services; - const { - services: { dataViewEditor }, - } = useMlKibana(); - - const nextStepPath = '/data_drift'; - const onObjectSelection = (id: string, type: string) => { - navigateToPath( - `${nextStepPath}?${type === 'index-pattern' ? 'index' : 'savedSearchId'}=${encodeURIComponent( - id - )}` - ); - }; - - const canEditDataView = dataViewEditor?.userPermissions.editDataView(); - - return ( -
- - - - } - /> - - - 'discoverApp', - name: i18n.translate( - 'xpack.ml.newJob.wizard.searchSelection.savedObjectType.discoverSession', - { - defaultMessage: 'Discover session', - } - ), - showSavedObject: (savedObject: SavedObject) => - // ES|QL Based saved searches are not supported in Data Drift, filter them out - !isEsqlSavedSearch(savedObject), - }, - { - type: 'index-pattern', - getIconForSavedObject: () => 'indexPatternApp', - name: i18n.translate( - 'xpack.ml.newJob.wizard.searchSelection.savedObjectType.dataView', - { - defaultMessage: 'Data view', - } - ), - }, - ]} - fixedPageSize={20} - services={{ - contentClient: contentManagement.client, - uiSettings, - }} - > - navigateToPath(createPath(ML_PAGES.DATA_DRIFT_CUSTOM))} - disabled={!canEditDataView} - data-test-subj={'dataDriftCreateDataViewButton'} - > - - - - - -
- ); -}; - export const DataDriftIndexPatternsPicker: FC = () => { const { reference, comparison } = parse(location.search, { sort: false, diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts index f186b96f61c82..60125b59eb228 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts @@ -126,28 +126,28 @@ export const AIOPS_BREADCRUMB_LOG_RATE_ANALYSIS: ChromeBreadcrumb = Object.freez text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { defaultMessage: 'AIOps Labs', }), - href: '/aiops/log_rate_analysis_index_select', + href: '/aiops/log_rate_analysis', }); export const AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { defaultMessage: 'AIOps Labs', }), - href: '/aiops/log_categorization_index_select', + href: '/aiops/log_categorization', }); export const AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { defaultMessage: 'AIOps Labs', }), - href: '/aiops/change_point_detection_index_select', + href: '/aiops/change_point_detection', }); export const LOG_RATE_ANALYSIS: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiops.logRateAnalysisBreadcrumbLabel', { defaultMessage: 'Log rate analysis', }), - href: '/aiops/log_rate_analysis_index_select', + href: '/aiops/log_rate_analysis', deepLinkId: 'ml:logRateAnalysis', }); @@ -162,7 +162,7 @@ export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel', { defaultMessage: 'Log pattern analysis', }), - href: '/aiops/log_categorization_index_select', + href: '/aiops/log_categorization', deepLinkId: 'ml:logPatternAnalysis', }); @@ -177,7 +177,7 @@ export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiops.changePointDetectionBreadcrumbLabel', { defaultMessage: 'Change point detection', }), - href: '/aiops/change_point_detection_index_select', + href: '/aiops/change_point_detection', deepLinkId: 'ml:changePointDetections', }); @@ -193,7 +193,7 @@ export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.settings.breadcrumbs.dataComparisonLabel', { defaultMessage: 'Data drift', }), - href: '/data_drift_index_select', + href: '/data_drift', deepLinkId: 'ml:dataDrift', }); diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/index_or_search.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/index_or_search.tsx index f141af6589923..14706032e4550 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/index_or_search.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/index_or_search.tsx @@ -5,88 +5,19 @@ * 2.0. */ -import type { FC } from 'react'; import React from 'react'; import { Redirect } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import { dynamic } from '@kbn/shared-ux-utility'; import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; -import type { NavigateToPath } from '../../../contexts/kibana'; -import { useMlKibana } from '../../../contexts/kibana'; -import type { MlRoute, PageProps } from '../../router'; -import { createPath, PageLoader } from '../../router'; -import { useRouteResolver } from '../../use_resolver'; -import { basicResolvers } from '../../resolvers'; -import { preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search'; -import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; +import type { MlRoute } from '../../router'; +import { createPath } from '../../router'; -enum MODE { - NEW_JOB, - DATAVISUALIZER, -} - -const Page = dynamic(async () => ({ - default: (await import('../../../jobs/new_job/pages/index_or_search')).Page, -})); - -interface IndexOrSearchPageProps extends PageProps { - nextStepPath: string; - mode: MODE; - extraButtons?: React.ReactNode; - entryPoint?: string; -} - -const getLogRateAnalysisBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ - getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_RATE_ANALYSIS', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('LOG_RATE_ANALYSIS', navigateToPath, basePath), - { - text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - }, -]; - -const getLogCategorizationBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ - getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('LOG_PATTERN_ANALYSIS', navigateToPath, basePath), - { - text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - }, -]; - -const getChangePointDetectionBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ - getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('CHANGE_POINT_DETECTION', navigateToPath, basePath), - { - text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - }, -]; - -export const logRateAnalysisIndexOrSearchRouteFactory = ( - navigateToPath: NavigateToPath, - basePath: string -): MlRoute => ({ +export const logRateAnalysisIndexOrSearchRouteFactory = (): MlRoute => ({ id: 'data_view_log_rate_analysis', path: createPath(ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT), - title: i18n.translate('xpack.ml.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - render: (props, deps) => ( - + render: ({ location }) => ( + ), - breadcrumbs: getLogRateAnalysisBreadcrumbs(navigateToPath, basePath), + breadcrumbs: [], }); /** @@ -95,74 +26,23 @@ export const logRateAnalysisIndexOrSearchRouteFactory = ( export const explainLogRateSpikesIndexOrSearchRouteFactory = (): MlRoute => ({ path: createPath(ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT), render: () => , - // no breadcrumbs since it's just a redirect breadcrumbs: [], }); -export const logCategorizationIndexOrSearchRouteFactory = ( - navigateToPath: NavigateToPath, - basePath: string -): MlRoute => ({ +export const logCategorizationIndexOrSearchRouteFactory = (): MlRoute => ({ id: 'data_view_log_categorization', path: createPath(ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT), - title: i18n.translate('xpack.ml.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - render: (props, deps) => ( - + render: ({ location }) => ( + ), - breadcrumbs: getLogCategorizationBreadcrumbs(navigateToPath, basePath), + breadcrumbs: [], }); -export const changePointDetectionIndexOrSearchRouteFactory = ( - navigateToPath: NavigateToPath, - basePath: string -): MlRoute => ({ +export const changePointDetectionIndexOrSearchRouteFactory = (): MlRoute => ({ id: 'data_view_change_point_detection', path: createPath(ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT), - title: i18n.translate('xpack.ml.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - render: (props, deps) => ( - + render: ({ location }) => ( + ), - breadcrumbs: getChangePointDetectionBreadcrumbs(navigateToPath, basePath), + breadcrumbs: [], }); - -// TODO: update PageWrapper - no longer need job creation items -const PageWrapper: FC = ({ nextStepPath, mode, extraButtons }) => { - const { - services: { - http: { basePath }, - application: { navigateToUrl }, - data: { dataViews: dataViewsService }, - }, - } = useMlKibana(); - - const newJobResolvers = { - ...basicResolvers(), - preConfiguredJobRedirect: () => - preConfiguredJobRedirect(dataViewsService, basePath.get(), navigateToUrl), - }; - - const { context } = useRouteResolver( - mode === MODE.NEW_JOB ? 'full' : 'basic', - mode === MODE.NEW_JOB ? ['canCreateJob'] : [], - mode === MODE.NEW_JOB ? newJobResolvers : {} - ); - return ( - - - - ); -}; diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_comparison.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_comparison.tsx index 9765587678b59..3cf213e93db98 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_comparison.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_comparison.tsx @@ -15,11 +15,7 @@ import type { NavigateToPath } from '../../../contexts/kibana'; import type { MlRoute, PageProps } from '../../router'; import { createPath, PageLoader } from '../../router'; import { useRouteResolver } from '../../use_resolver'; -import { - breadcrumbOnClickFactory, - DATA_DRIFT_BREADCRUMB, - getBreadcrumbWithUrlForApp, -} from '../../breadcrumbs'; +import { DATA_DRIFT_BREADCRUMB, getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; import { basicResolvers } from '../../resolvers'; const DataDriftPage = dynamic(async () => ({ @@ -41,17 +37,6 @@ export const dataDriftRouteFactory = ( getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), { text: DATA_DRIFT_BREADCRUMB.text, - ...(navigateToPath - ? { - href: `${basePath}/app/ml${DATA_DRIFT_BREADCRUMB.href}`, - onClick: breadcrumbOnClickFactory(DATA_DRIFT_BREADCRUMB.href, navigateToPath), - } - : {}), - }, - { - text: i18n.translate('xpack.ml.trainedModelsBreadcrumbs.dataDriftResultsLabel', { - defaultMessage: 'Results', - }), }, ], 'data-test-subj': 'mlPageDataDrift', diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_drift.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_drift.tsx index 5c9762599e6bf..3dffc2ba5120b 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_drift.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/data_drift.tsx @@ -8,56 +8,25 @@ import { i18n } from '@kbn/i18n'; import type { FC } from 'react'; import React from 'react'; +import { Redirect } from 'react-router-dom'; import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; -import { - DataDriftIndexOrSearchRedirect, - DataDriftIndexPatternsPicker, -} from '../../../datavisualizer/data_drift/index_patterns_picker'; +import { DataDriftIndexPatternsPicker } from '../../../datavisualizer/data_drift/index_patterns_picker'; import type { NavigateToPath } from '../../../contexts/kibana'; import type { MlRoute } from '../..'; import type { PageProps } from '../../router'; import { createPath, PageLoader } from '../../router'; -import { - breadcrumbOnClickFactory, - DATA_DRIFT_INDEX_SELECT_BREADCRUMB, - DATA_VISUALIZER_BREADCRUMB, - DATA_DRIFT_BREADCRUMB, - getBreadcrumbWithUrlForApp, -} from '../../breadcrumbs'; +import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; import { useRouteResolver } from '../../use_resolver'; import { basicResolvers } from '../../resolvers'; import { DataSourceContextProvider } from '../../../contexts/ml'; -export const dataDriftRouteIndexOrSearchFactory = ( - navigateToPath: NavigateToPath, - basePath: string -): MlRoute => ({ +export const dataDriftRouteIndexOrSearchFactory = (): MlRoute => ({ id: 'dataDrift', path: createPath(ML_PAGES.DATA_DRIFT_INDEX_SELECT), - title: i18n.translate('xpack.ml.dataVisualizer.dataDrift.docTitle', { - defaultMessage: 'Data Drift', - }), - render: (props, deps) => ( - + render: ({ location }) => ( + ), - breadcrumbs: [ - getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), - { - text: DATA_DRIFT_BREADCRUMB.text, - ...(navigateToPath - ? { - href: `${basePath}/app/ml${DATA_DRIFT_BREADCRUMB.href}`, - onClick: breadcrumbOnClickFactory(DATA_DRIFT_BREADCRUMB.href, navigateToPath), - } - : {}), - }, - { - text: i18n.translate('xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel', { - defaultMessage: 'Select Data View', - }), - }, - ], + breadcrumbs: [], 'data-test-subj': 'mlPageDataDrift', }); @@ -70,21 +39,10 @@ export const dataDriftRouteIndexPatternFactory = ( title: i18n.translate('xpack.ml.dataVisualizer.dataDriftCustomIndexPatterns.docTitle', { defaultMessage: 'Data Drift Custom Index Patterns', }), - render: (props, deps) => , + render: (props, deps) => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - { - text: DATA_VISUALIZER_BREADCRUMB.text, - ...(navigateToPath - ? { - href: `${basePath}/app/ml${DATA_DRIFT_INDEX_SELECT_BREADCRUMB.href}`, - onClick: breadcrumbOnClickFactory( - DATA_DRIFT_INDEX_SELECT_BREADCRUMB.href, - navigateToPath - ), - } - : {}), - }, + getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), { text: i18n.translate('xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel', { defaultMessage: 'Data Drift', @@ -94,20 +52,14 @@ export const dataDriftRouteIndexPatternFactory = ( 'data-test-subj': 'mlPageDataDriftCustomIndexPatterns', }); -interface DataDriftPageProps extends PageProps { - mode: 'data_drift_index_select' | 'data_drift_custom'; -} -const PageWrapper: FC = ({ mode }) => { +type DataDriftPageProps = PageProps; +const PageWrapper: FC = () => { const { context } = useRouteResolver('full', [], basicResolvers()); return ( - {mode === ML_PAGES.DATA_DRIFT_INDEX_SELECT ? ( - - ) : ( - - )} + ); diff --git a/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.ts b/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.ts index 0d065eb673c2c..12f05622dd888 100644 --- a/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.ts +++ b/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.ts @@ -52,12 +52,15 @@ export class MlLocatorDefinition implements LocatorDefinition { case ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION: path = formatDataFrameAnalyticsExplorationUrl('', params.pageState); break; - case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION: - path = formatChangePointDetectionUrl( - '', - params.pageState as ChangePointDetectionQueryState - ); + case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION: { + const cpState = params.pageState as ChangePointDetectionQueryState; + if (cpState?.fieldConfigs && cpState?.index) { + path = formatChangePointDetectionUrl('', cpState); + } else { + path = formatGenericMlUrl('', params.page, params.pageState); + } break; + } default: path = formatGenericMlUrl('', params.page, params.pageState); break; diff --git a/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts b/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts index 8810503e07c59..52dda2e737bca 100644 --- a/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts +++ b/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts @@ -131,15 +131,14 @@ function createDeepLinks( title: i18n.translate('xpack.ml.deepLink.aiOps', { defaultMessage: 'AIOps', }), - // Default to the index select page for log rate analysis since we don't have an AIops overview page - path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`, + path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS}`, deepLinks: [ { id: 'logRateAnalysis', title: i18n.translate('xpack.ml.deepLink.logRateAnalysis', { defaultMessage: 'Log rate analysis', }), - path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT}`, + path: `/${ML_PAGES.AIOPS_LOG_RATE_ANALYSIS}`, }, { id: 'logRateAnalysisPage', @@ -154,7 +153,7 @@ function createDeepLinks( title: i18n.translate('xpack.ml.deepLink.logPatternAnalysis', { defaultMessage: 'Log pattern analysis', }), - path: `/${ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT}`, + path: `/${ML_PAGES.AIOPS_LOG_CATEGORIZATION}`, }, { id: 'logPatternAnalysisPage', @@ -169,7 +168,7 @@ function createDeepLinks( title: i18n.translate('xpack.ml.deepLink.changePointDetection', { defaultMessage: 'Change point detection', }), - path: `/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT}`, + path: `/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION}`, }, { id: 'changePointDetectionsPage', @@ -254,7 +253,7 @@ function createDeepLinks( title: i18n.translate('xpack.ml.deepLink.dataDrift', { defaultMessage: 'Data drift', }), - path: `/${ML_PAGES.DATA_DRIFT_INDEX_SELECT}`, + path: `/${ML_PAGES.DATA_DRIFT}`, }; }, getDataDriftPageDeepLink: (): AppDeepLink => { From b9648689312f4cd4c3415fe7568bb8f53481bdbf Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 11 May 2026 14:13:50 +0200 Subject: [PATCH 02/32] feat(ml): enhance DataSourcePicker to require time-based data views - Updated DataSourcePicker component to include a new prop `requireTimeBased`, which filters data views to only those with a time field. - Modified ChangePointDetectionPage, LogCategorizationPage, and LogRateAnalysisPage to utilize the updated DataSourcePicker with the new prop. - Removed obsolete AIOps breadcrumbs from routing files to streamline navigation. This change improves the user experience by ensuring that only relevant data views are presented in the picker for time-sensitive analyses. --- .../aiops/change_point_detection.tsx | 6 ++- .../application/aiops/log_categorization.tsx | 6 ++- .../application/aiops/log_rate_analysis.tsx | 6 ++- .../data_source_picker/data_source_picker.tsx | 13 ++++- .../index_based/index_data_visualizer.tsx | 10 +++- .../overview/data_visualizer_grid.tsx | 2 +- .../public/application/routing/breadcrumbs.ts | 26 ---------- .../routes/aiops/change_point_detection.tsx | 1 - .../routes/aiops/log_categorization.tsx | 1 - .../routes/aiops/log_rate_analysis.tsx | 1 - .../routing/routes/data_view_select.tsx | 52 +++---------------- 11 files changed, 43 insertions(+), 81 deletions(-) diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index d126ba6a073d4..56b8870d3f74b 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -39,7 +39,11 @@ export const ChangePointDetectionPage: FC = () => { ); const headerContent = ( - + ); return ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx index e0464ee7c4427..9837d7e596b8a 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx @@ -35,7 +35,11 @@ export const LogCategorizationPage: FC = () => { ); const headerContent = ( - + ); return ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index cfe212c4581a7..f5aacb0bb075f 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -32,7 +32,11 @@ export const LogRateAnalysisPage: FC = () => { ); const headerContent = ( - + ); return ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx index 4f7dacb16eb0d..f03d446d04ad4 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx @@ -26,7 +26,9 @@ import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-fin import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; -type SavedObject = SavedObjectCommon; +type SavedObject = SavedObjectCommon< + FinderAttributes & { isTextBasedQuery?: boolean; timeFieldName?: string } +>; const pickerPanelCss = css({ width: 600, maxHeight: '70vh', overflow: 'auto' }); @@ -40,6 +42,8 @@ export interface DataSourcePickerProps { onCreateDataView?: () => void; /** data-test-subj for the "Create a data view" button */ createDataViewButtonTestSubj?: string; + /** When true, only data views with a time field are shown in the picker. */ + requireTimeBased?: boolean; } export const DataSourcePicker: FC = ({ @@ -47,6 +51,7 @@ export const DataSourcePicker: FC = ({ currentSavedSearch, onCreateDataView, createDataViewButtonTestSubj = 'mlDataSourcePickerCreateDataViewButton', + requireTimeBased = false, }) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const navigateToPath = useNavigateToPath(); @@ -164,6 +169,12 @@ export const DataSourcePicker: FC = ({ name: i18n.translate('xpack.ml.dataSourcePicker.savedObjectType.dataView', { defaultMessage: 'Data view', }), + ...(requireTimeBased + ? { + showSavedObject: (savedObject: SavedObject) => + !!savedObject.attributes.timeFieldName, + } + : {}), }, ]} fixedPageSize={20} diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 4a9f1ca7a92fc..ac5f9f56e060c 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -26,8 +26,11 @@ import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_ import { checkPermission } from '../../capabilities/check_capabilities'; import { MlPageHeader } from '../../components/page_header'; import { useEnabledFeatures } from '../../contexts/ml'; +import { useDataSource } from '../../contexts/ml/data_source_context'; import { useMlManagementLocator } from '../../contexts/kibana/use_create_url'; import { PageTitle } from '../../components/page_title'; +import { DataSourcePicker } from '../../components/data_source_picker/data_source_picker'; + export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); const { @@ -44,6 +47,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) } = useMlKibana(); const mlApi = useMlApi(); const { showNodeInfo } = useEnabledFeatures(); + const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); const mlLocator = useMlLocator()!; const mlManagementLocator = useMlManagementLocator(); const mlFeaturesDisabled = !isFullLicense(); @@ -190,11 +194,15 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) [mlLocator, mlFeaturesDisabled] ); + const dataSourcePicker = !esql ? ( + + ) : undefined; + return IndexDataVisualizer ? ( {IndexDataVisualizer !== null ? ( <> - + , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION', navigateToPath, basePath), { text: i18n.translate('xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel', { defaultMessage: 'Change point detection', diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_categorization.tsx index 2e5c81de0e7a5..10197d742f9e9 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_categorization.tsx @@ -33,7 +33,6 @@ export const logCategorizationRouteFactory = ( render: () => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS', navigateToPath, basePath), { text: i18n.translate('xpack.ml.aiops.logCategorization.docTitle', { defaultMessage: 'Log pattern analysis', diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_rate_analysis.tsx index f649874178b3e..a1c6645df2278 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/log_rate_analysis.tsx @@ -34,7 +34,6 @@ export const logRateAnalysisRouteFactory = ( render: () => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_RATE_ANALYSIS', navigateToPath, basePath), { text: i18n.translate('xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel', { defaultMessage: 'Log rate analysis', diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx index c881a01578c81..fbc26a9be9bbf 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx @@ -6,56 +6,16 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { Redirect } from 'react-router-dom'; import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; import type { MlRoute } from '../router'; -import type { NavigateToPath } from '../../contexts/kibana'; -import { NavigateToPageButton } from '../components/navigate_to_page_button'; -import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { createPath } from '../router'; -import { MODE, PageWrapper } from './new_job/index_or_search_page_wrapper'; -const getDataVisBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ - getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), - { - text: i18n.translate('xpack.ml.jobsBreadcrumbs.selectDateViewLabel', { - defaultMessage: 'Select Data View', - }), - }, -]; - -export const dataVizIndexOrSearchRouteFactory = ( - navigateToPath: NavigateToPath, - basePath: string -): MlRoute => ({ +export const dataVizIndexOrSearchRouteFactory = (): MlRoute => ({ id: 'data_view_datavisualizer', path: createPath(ML_PAGES.DATA_VISUALIZER_INDEX_SELECT), - title: i18n.translate('xpack.ml.selectDataViewLabel', { - defaultMessage: 'Select Data View', - }), - render: (props, deps) => { - const button = ( - - } - /> - ); - return ( - - ); - }, - breadcrumbs: getDataVisBreadcrumbs(navigateToPath, basePath), + render: ({ location }) => ( + + ), + breadcrumbs: [], }); From b657957a601ed93c68b89f45b5f862ddc5d5e36f Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 11 May 2026 14:43:10 +0200 Subject: [PATCH 03/32] fix(ml): update navigation paths for AIOps buttons in OverviewPage - Changed the navigation paths for the Log Categorization, Log Rate Analysis, and Change Point Detection buttons in the OverviewPage component to reflect updated routes. - This improves the accuracy of navigation within the AIOps section of the application. --- .../ml/public/application/overview/overview_ml_page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx index ff2310487afdd..9f425680b2f46 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx @@ -226,7 +226,7 @@ export const OverviewPage: FC = () => { navigateToPath('/aiops/log_categorization_index_select')} + onClick={() => navigateToPath('/aiops/log_categorization')} data-test-subj="mlOverviewCardLogPatternAnalysisButton" > @@ -265,7 +265,7 @@ export const OverviewPage: FC = () => { navigateToPath('/aiops/log_rate_analysis_index_select')} + onClick={() => navigateToPath('/aiops/log_rate_analysis')} data-test-subj="mlOverviewCardLogRateAnalysisButton" > @@ -305,7 +305,7 @@ export const OverviewPage: FC = () => { color="text" target="_self" onClick={() => - navigateToPath('/aiops/change_point_detection_index_select') + navigateToPath('/aiops/change_point_detection') } data-test-subj="mlOverviewCardChangePointDetectionButton" aria-label={i18n.translate( From 7710de033b2dbb4da2b938381a36b0c24f60ae48 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 11 May 2026 19:27:10 +0200 Subject: [PATCH 04/32] feat(ml): add header content support to data visualizer components - Enhanced the DataVisualizerStateContextProvider and IndexDataVisualizer components to accept a new `headerContent` prop, allowing for customizable header content. - Updated IndexDataVisualizerView to render the `headerContent` within the layout, improving flexibility for displaying additional information. - Implemented similar changes in ChangePointDetectionPage, LogCategorizationPage, and LogRateAnalysisPage to handle scenarios where no data view is selected, displaying an empty prompt along with the header content. This update enhances the user experience by providing more context and information in the data visualizer components. --- .../index_data_visualizer_view.tsx | 91 +++++++++--------- .../index_data_visualizer.tsx | 17 +++- .../aiops/change_point_detection.tsx | 92 ++++++++++++------- .../application/aiops/log_categorization.tsx | 87 +++++++++++------- .../application/aiops/log_rate_analysis.tsx | 91 +++++++++++------- .../contexts/ml/data_source_context.tsx | 2 + .../index_based/index_data_visualizer.tsx | 39 ++++++-- 7 files changed, 268 insertions(+), 151 deletions(-) diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index 763b13713b4e6..c88714767cb91 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -5,14 +5,12 @@ * 2.0. */ -import { css } from '@emotion/react'; -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useEffect, useMemo, useState, useCallback, useRef } from 'react'; import type { Required } from 'utility-types'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; import { - useEuiBreakpoint, useIsWithinMaxBreakpoint, EuiFlexGroup, EuiFlexItem, @@ -104,9 +102,13 @@ export interface IndexDataVisualizerViewProps { currentSavedSearch: SavedSearch | null; currentSessionId?: string; getAdditionalLinks?: GetAdditionalLinks; + headerContent?: ReactNode; } -export const IndexDataVisualizerView: FC = (dataVisualizerProps) => { +export const IndexDataVisualizerView: FC = ({ + headerContent, + ...dataVisualizerProps +}) => { const [savedRandomSamplerPreference, saveRandomSamplerPreference] = useStorage< DVKey, DVStorageMapped @@ -458,12 +460,6 @@ export const IndexDataVisualizerView: FC = (dataVi ); const isWithinLargeBreakpoint = useIsWithinMaxBreakpoint('l'); - const dvPageHeader = css({ - [useEuiBreakpoint(['xs', 's', 'm', 'l'])]: { - flexDirection: 'column', - alignItems: 'flex-start', - }, - }); const queryNeedsUpdate = useMemo( () => (localQueryString !== dataVisualizerListState.searchString ? true : undefined), @@ -519,40 +515,47 @@ export const IndexDataVisualizerView: FC = (dataVi paddingSize="none" > - - {currentDataView.getName()} - {/* TODO: This management section shouldn't live inside the header */} - - - } - rightSideGroupProps={{ - gutterSize: 's', - 'data-test-subj': 'dataVisualizerTimeRangeSelectorSection', - }} - rightSideItems={[ - , - hasValidTimeField && ( - - ), - ]} - /> + + + + {headerContent ?? null} + + + + + + + + {hasValidTimeField && ( + + + + )} + + + + + + diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 172d25596f4a4..10f2e68621e86 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { pick } from 'lodash'; -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { parse, stringify } from 'query-string'; @@ -52,6 +52,7 @@ const localStorage = new Storage(window.localStorage); export interface DataVisualizerStateContextProviderProps { IndexDataVisualizerComponent: FC; getAdditionalLinks?: GetAdditionalLinks; + headerContent?: ReactNode; } export type IndexDataVisualizerSpec = typeof IndexDataVisualizer; @@ -113,6 +114,7 @@ const DataVisualizerESQLStateContextProvider = () => { const DataVisualizerStateContextProvider: FC = ({ IndexDataVisualizerComponent, getAdditionalLinks, + headerContent, }) => { const { services } = useDataVisualizerKibana(); const { @@ -209,6 +211,11 @@ const DataVisualizerStateContextProvider: FC null); + if (defaultDataView) { + setCurrentDataView(defaultDataView); + } } }; getDataView(); @@ -284,8 +291,11 @@ const DataVisualizerStateContextProvider: FC - ) : null} + ) : ( + headerContent ?? null + )} ); }; @@ -294,12 +304,14 @@ export interface Props { getAdditionalLinks?: GetAdditionalLinks; showFrozenDataTierChoice?: boolean; esql?: boolean; + headerContent?: ReactNode; } export const IndexDataVisualizer: FC = ({ getAdditionalLinks, showFrozenDataTierChoice = true, esql, + headerContent, }) => { const coreStart = getCoreStart(); const { @@ -352,6 +364,7 @@ export const IndexDataVisualizer: FC = ({ ) : ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index 56b8870d3f74b..ae5a19bf88bbc 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -14,7 +14,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { ChangePointDetection } from '@kbn/aiops-plugin/public'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import { useFieldStatsTrigger, FieldStatsFlyoutProvider } from '@kbn/ml-field-stats-flyout'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDataSource } from '../contexts/ml/data_source_context'; import { useMlKibana } from '../contexts/kibana'; @@ -58,39 +58,63 @@ export const ChangePointDetectionPage: FC = () => {
- + {!dataView ? ( + <> + {headerContent} + + + + } + body={ +

+ +

+ } + /> + + ) : ( + + )} { - + {!dataView ? ( + <> + {headerContent} + + + + } + body={ +

+ +

+ } + /> + + ) : ( + + )} ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index f5aacb0bb075f..2f0c9369de9db 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -12,6 +12,7 @@ import { pick } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { LogRateAnalysis } from '@kbn/aiops-plugin/public'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { useDataSource } from '../contexts/ml/data_source_context'; import { useMlKibana } from '../contexts/kibana'; @@ -44,39 +45,63 @@ export const LogRateAnalysisPage: FC = () => { - + {!dataView ? ( + <> + {headerContent} + + + + } + body={ +

+ +

+ } + /> + + ) : ( + + )} ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx index 5855325f5918f..44675e6f4927f 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx @@ -82,6 +82,8 @@ export const DataSourceContextProvider: FC> = ({ chil dataViewAndSavedSearch = await getDataViewAndSavedSearchCb(savedSearchId); } else if (dataViewId !== undefined) { dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId); + } else { + dataViewAndSavedSearch.dataView = (await dataViews.getDefaultDataView().catch(() => null)) ?? null; } const { savedSearch, dataView } = dataViewAndSavedSearch; diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index ac5f9f56e060c..e4e40511b09d2 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { IndexDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public'; import { useTimefilter } from '@kbn/ml-date-picker'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import type { GetAdditionalLinksParams, @@ -202,7 +202,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) {IndexDataVisualizer !== null ? ( <> - + = ({ esql = false }) ) : null} - + {!dataView && !esql ? ( + <> + {dataSourcePicker} + + + + } + body={ +

+ +

+ } + /> + + ) : ( + + )} ) : null} From 0071085a88293275d6eaad1c9edc3677f0b1206d Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Thu, 14 May 2026 17:59:07 +0200 Subject: [PATCH 05/32] refactor(ml): reuse DataViewPicker --- .../plugins/shared/discover/public/plugin.tsx | 20 ++ .../plugins/shared/discover/public/types.ts | 4 + .../dataview_picker/change_dataview.tsx | 5 +- .../dataview_picker/data_view_picker.tsx | 7 + .../data_drift/data_drift_page.tsx | 14 +- .../data_view_management.tsx | 125 ---------- .../components/data_view_management/index.ts | 8 - .../index_data_visualizer_view.tsx | 24 +- .../translations/translations/de-DE.json | 9 - .../translations/translations/fr-FR.json | 9 - .../translations/translations/ja-JP.json | 9 - .../translations/translations/zh-CN.json | 9 - .../change_point_detection_root.tsx | 10 +- .../log_categorization_app_state.tsx | 11 +- .../log_rate_analysis_app_state.tsx | 11 +- .../platform/plugins/shared/ml/kibana.jsonc | 1 + .../aiops/change_point_detection.tsx | 5 +- .../application/aiops/log_categorization.tsx | 5 +- .../application/aiops/log_rate_analysis.tsx | 5 +- .../shared/ml/public/application/app.tsx | 2 + .../data_source_picker/data_source_picker.tsx | 216 ------------------ .../ml_data_source_picker.tsx | 136 +++++++++++ .../contexts/kibana/kibana_context.ts | 4 + .../data_drift/data_drift_page.tsx | 11 +- .../index_based/index_data_visualizer.tsx | 37 +-- .../overview/data_visualizer_grid.tsx | 8 +- .../routes/datavisualizer/index_based.tsx | 8 +- .../plugins/shared/ml/public/plugin.ts | 6 + .../platform/plugins/shared/ml/tsconfig.json | 1 + 29 files changed, 270 insertions(+), 450 deletions(-) delete mode 100644 x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx delete mode 100644 x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/index.ts delete mode 100644 x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx create mode 100644 x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx diff --git a/src/platform/plugins/shared/discover/public/plugin.tsx b/src/platform/plugins/shared/discover/public/plugin.tsx index a329770f60aff..a6fcf221acf3d 100644 --- a/src/platform/plugins/shared/discover/public/plugin.tsx +++ b/src/platform/plugins/shared/discover/public/plugin.tsx @@ -29,6 +29,7 @@ import { ADD_PANEL_TRIGGER, ON_OPEN_PANEL_MENU } from '@kbn/ui-actions-plugin/co import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { ProjectRoutingAccess } from '@kbn/cps-utils'; import type { DiscoverSessionAttributes } from '@kbn/saved-search-plugin/server'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { DISCOVER_APP_LOCATOR, PLUGIN_ID, type DiscoverAppLocator } from '../common'; import { DISCOVER_CONTEXT_APP_LOCATOR, @@ -65,6 +66,8 @@ import type { ProfileProviderSharedServices, ProfilesManager } from './context_a import { forwardLegacyUrls } from './plugin_imports/forward_legacy_urls'; import { registerEsqlResultsAttachmentUi } from './agent_builder/register_esql_results_ui'; import { getProfilesInspectorView } from './context_awareness/inspector/get_profiles_inspector_view'; +import { OpenSearchPanel } from './application/main/components/top_nav/open_search_panel'; +import type { DiscoverServices } from './build_services'; /** * Contains Discover, one of the oldest parts of Kibana @@ -304,6 +307,23 @@ export class DiscoverPlugin DiscoverContainer: (props: DiscoverContainerProps) => ( ), + OpenSessionPanel: (props: { + onClose: () => void; + onOpenSavedSearch: (id: string) => void; + }) => { + const openSessionServices = { + addBasePath: (path: string) => core.http.basePath.prepend(path), + capabilities: core.application.capabilities, + savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), + contentClient: plugins.contentManagement.client, + uiSettings: core.uiSettings, + }; + return ( + + + + ); + }, }; } diff --git a/src/platform/plugins/shared/discover/public/types.ts b/src/platform/plugins/shared/discover/public/types.ts index eb629c6d9770f..f91ebb649e95d 100644 --- a/src/platform/plugins/shared/discover/public/types.ts +++ b/src/platform/plugins/shared/discover/public/types.ts @@ -127,6 +127,10 @@ export interface DiscoverStart { * Use the Discover context awareness framework instead to register a custom Discover profile. */ readonly DiscoverContainer: ComponentType; + readonly OpenSessionPanel: ComponentType<{ + onClose: () => void; + onOpenSavedSearch: (id: string) => void; + }>; } /** diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx index ef152b4c37f80..7112ecf8f67ae 100644 --- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx @@ -66,6 +66,7 @@ export function ChangeDataView({ onCreateDefaultAdHocDataView, onClosePopover, getDataViewHelpText, + compressed = true, }: DataViewPickerProps) { const { euiTheme } = useEuiTheme(); const [isPopoverOpen, setPopoverIsOpen] = useState(false); @@ -126,7 +127,7 @@ export function ChangeDataView({ const { label, title, 'data-test-subj': dataTestSubj, fullWidth, ...rest } = trigger; return ( { return ( ); }; diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx index c612b2f684ef5..dfcb188fb1763 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { css } from '@emotion/react'; import type { FC, ReactNode } from 'react'; import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; @@ -55,6 +56,11 @@ import { useSearch } from '../common/hooks/use_search'; import { DocumentCountWithBrush } from './document_count_with_brush'; import { useDataDriftColors } from './use_data_drift_colors'; +const maxInlineSizeStyles = css` + max-inline-size: 100%; + min-inline-size: 0; +`; + interface PageHeaderProps { onRefresh: () => void; needsUpdate: boolean; @@ -100,12 +106,12 @@ export const PageHeader: FC = ({ onRefresh, needsUpdate, header gutterSize="s" alignItems="center" justifyContent="spaceBetween" - responsive={false} + wrap={true} data-test-subj="dataComparisonTimeRangeSelectorSection" > {headerContent ?? null} - - + + {hasValidTimeField && ( = ({ onRefresh, needsUpdate, header /> )} - + void | undefined>(); - useEffect(() => { - return () => { - // Make sure to close the editor when unmounting - if (closeFieldEditor.current) { - closeFieldEditor.current(); - } - }; - }, []); - - if (dataViewFieldEditor === undefined || !currentDataView || !canEditDataViewField) { - return null; - } - - const addField = async () => { - closeFieldEditor.current = await dataViewFieldEditor.openEditor({ - ctx: { - dataView: currentDataView, - }, - onSave: () => { - const refresh: Refresh = { - lastRefresh: Date.now(), - }; - mlTimefilterRefresh$.next(refresh); - }, - }); - }; - - return ( - { - setIsAddDataViewFieldPopoverOpen(false); - }} - ownFocus - data-test-subj="dataVisualizerDataViewManagementPopover" - aria-label={i18n.translate('xpack.dataVisualizer.index.dataViewManagement.popoverAriaLabel', { - defaultMessage: 'Data view management', - })} - button={ - { - setIsAddDataViewFieldPopoverOpen(!isAddDataViewFieldPopoverOpen); - }} - /> - } - > - { - setIsAddDataViewFieldPopoverOpen(false); - addField(); - }} - > - {i18n.translate('xpack.dataVisualizer.index.dataViewManagement.addFieldButton', { - defaultMessage: 'Add field to data view', - })} - , - { - setIsAddDataViewFieldPopoverOpen(false); - application.navigateToApp('management', { - path: `/kibana/dataViews/dataView/${props.currentDataView?.id}`, - }); - }} - > - {i18n.translate('xpack.dataVisualizer.index.dataViewManagement.manageFieldButton', { - defaultMessage: 'Manage data view fields', - })} - , - ]} - /> - - ); -} diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/index.ts b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/index.ts deleted file mode 100644 index c3a178955a03c..0000000000000 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { DataVisualizerDataViewManagement } from './data_view_management'; diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index c88714767cb91..ffd6210ba44b9 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { css } from '@emotion/react'; import type { FC, ReactNode } from 'react'; import React, { useEffect, useMemo, useState, useCallback, useRef } from 'react'; import type { Required } from 'utility-types'; @@ -62,7 +63,6 @@ import { DocumentCountContent } from '../../../common/components/document_count_ import { OMIT_FIELDS } from '../../../../../common/constants'; import { SearchPanel } from '../search_panel'; import { ActionsPanel } from '../actions_panel'; -import { DataVisualizerDataViewManagement } from '../data_view_management'; import type { GetAdditionalLinks } from '../../../common/components/results_links'; import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; import { @@ -72,6 +72,11 @@ import { } from '../../constants/random_sampler'; import type { FieldStatisticTableEmbeddableProps } from '../../embeddables/grid_embeddable/types'; +const maxInlineSizeStyles = css` + max-inline-size: 100%; + min-inline-size: 0; +`; + const defaultSearchQuery = { match_all: {}, }; @@ -519,19 +524,12 @@ export const IndexDataVisualizerView: FC = ({ gutterSize="s" alignItems="center" justifyContent="spaceBetween" - responsive={false} + wrap={true} data-test-subj="dataVisualizerTimeRangeSelectorSection" > - - - {headerContent ?? null} - - - - - - - + {headerContent ?? null} + + {hasValidTimeField && ( = ({ /> )} - + const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); if (warning !== null) { - return <>{warning}; + return ( + + + {headerContent} + + {warning} + + + ); } appContextValue.embeddingOrigin = AIOPS_EMBEDDABLE_ORIGIN.ML_AIOPS_LABS; diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx index 5ed0284003437..38198a2dc87b1 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx @@ -6,6 +6,7 @@ */ import type { FC, ReactNode } from 'react'; import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; import { pick } from 'lodash'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; @@ -70,7 +71,15 @@ export const LogCategorizationAppState: FC = ({ const warning = timeSeriesDataViewWarning(dataView, 'log_categorization'); if (warning !== null) { - return <>{warning}; + return ( + + + {headerContent} + + {warning} + + + ); } const datePickerDeps: DatePickerDependencies = { diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index c299c230a6b00..0308a444d973c 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -7,6 +7,7 @@ import type { FC, ReactNode } from 'react'; import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; import { pick } from 'lodash'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; @@ -76,7 +77,15 @@ export const LogRateAnalysisAppState: FC = ({ const warning = timeSeriesDataViewWarning(dataView, 'log_rate_analysis'); if (warning !== null) { - return <>{warning}; + return ( + + + {headerContent} + + {warning} + + + ); } const CasesContext = appContextValue.cases?.ui.getCasesContext() ?? React.Fragment; const casesPermissions = appContextValue.cases?.helpers.canUseCases(); diff --git a/x-pack/platform/plugins/shared/ml/kibana.jsonc b/x-pack/platform/plugins/shared/ml/kibana.jsonc index cff765806a859..e3af20d10cad2 100644 --- a/x-pack/platform/plugins/shared/ml/kibana.jsonc +++ b/x-pack/platform/plugins/shared/ml/kibana.jsonc @@ -16,6 +16,7 @@ "cloud", "data", "dataViewEditor", + "dataViewFieldEditor", "dataViews", "dataVisualizer", "discover", diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index ae5a19bf88bbc..d4bb9a47fb4ed 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -23,7 +23,7 @@ import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; import { useEnabledFeatures } from '../contexts/ml/serverless_context'; -import { DataSourcePicker } from '../components/data_source_picker/data_source_picker'; +import { MlDataSourcePicker } from '../components/ml_data_source_picker/ml_data_source_picker'; export const ChangePointDetectionPage: FC = () => { const { services } = useMlKibana(); @@ -39,10 +39,9 @@ export const ChangePointDetectionPage: FC = () => { ); const headerContent = ( - ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx index e853c9cdc3b28..b1b3fb8979979 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx @@ -20,7 +20,7 @@ import { useEnabledFeatures } from '../contexts/ml'; import { HelpMenu } from '../components/help_menu'; import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; -import { DataSourcePicker } from '../components/data_source_picker/data_source_picker'; +import { MlDataSourcePicker } from '../components/ml_data_source_picker/ml_data_source_picker'; export const LogCategorizationPage: FC = () => { const { services } = useMlKibana(); @@ -36,10 +36,9 @@ export const LogCategorizationPage: FC = () => { ); const headerContent = ( - ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index 2f0c9369de9db..57f16f067bda5 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -20,7 +20,7 @@ import { HelpMenu } from '../components/help_menu'; import { useEnabledFeatures } from '../contexts/ml'; import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; -import { DataSourcePicker } from '../components/data_source_picker/data_source_picker'; +import { MlDataSourcePicker } from '../components/ml_data_source_picker/ml_data_source_picker'; export const LogRateAnalysisPage: FC = () => { const { services } = useMlKibana(); @@ -33,10 +33,9 @@ export const LogRateAnalysisPage: FC = () => { ); const headerContent = ( - ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/app.tsx b/x-pack/platform/plugins/shared/ml/public/application/app.tsx index 2a7ba176c5747..e9c8ca933e523 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/app.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/app.tsx @@ -82,10 +82,12 @@ export const App: FC = ({ ...coreStart, cases: deps.cases, charts: deps.charts, + discover: deps.discover, contentManagement: deps.contentManagement, dashboard: deps.dashboard, data: deps.data, dataViewEditor: deps.dataViewEditor, + dataViewFieldEditor: deps.dataViewFieldEditor, dataViews: deps.data.dataViews, dataVisualizer: deps.dataVisualizer, embeddable: deps.embeddable, diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx deleted file mode 100644 index f03d446d04ad4..0000000000000 --- a/x-pack/platform/plugins/shared/ml/public/application/components/data_source_picker/data_source_picker.tsx +++ /dev/null @@ -1,216 +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, { useCallback, useEffect, useRef, useState } from 'react'; -import { css } from '@emotion/react'; -import { useLocation } from 'react-router-dom'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiFormControlButton, - EuiFormControlLayout, - EuiPopover, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/common'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; - -import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; - -type SavedObject = SavedObjectCommon< - FinderAttributes & { isTextBasedQuery?: boolean; timeFieldName?: string } ->; - -const pickerPanelCss = css({ width: 600, maxHeight: '70vh', overflow: 'auto' }); - -export interface DataSourcePickerProps { - currentDataView: DataView | null; - currentSavedSearch: SavedSearch | null; - /** - * Called when the user clicks "Create a data view". When omitted, the standard - * data view editor flyout is opened and the page navigates to the newly created view. - */ - onCreateDataView?: () => void; - /** data-test-subj for the "Create a data view" button */ - createDataViewButtonTestSubj?: string; - /** When true, only data views with a time field are shown in the picker. */ - requireTimeBased?: boolean; -} - -export const DataSourcePicker: FC = ({ - currentDataView, - currentSavedSearch, - onCreateDataView, - createDataViewButtonTestSubj = 'mlDataSourcePickerCreateDataViewButton', - requireTimeBased = false, -}) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const navigateToPath = useNavigateToPath(); - const location = useLocation(); - const { contentManagement, uiSettings, dataViewEditor } = useMlKibana().services; - const closeDataViewEditorRef = useRef<() => void | undefined>(); - - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); - - const onChoose = (id: string, type: string) => { - setIsPopoverOpen(false); - const param = type === 'index-pattern' ? 'index' : 'savedSearchId'; - navigateToPath(`${location.pathname}?${param}=${encodeURIComponent(id)}`); - }; - - const openCreateDataViewFlyout = useCallback(() => { - setIsPopoverOpen(false); - closeDataViewEditorRef.current = dataViewEditor?.openEditor({ - onSave: (dataView) => { - if (dataView.id) { - navigateToPath(`${location.pathname}?index=${encodeURIComponent(dataView.id)}`); - } - }, - }); - }, [dataViewEditor, navigateToPath, location.pathname]); - - useEffect(function cleanUpFlyout() { - return () => { - if (closeDataViewEditorRef.current) { - closeDataViewEditorRef.current(); - } - }; - }, []); - - const handleCreateDataView = useCallback(() => { - setIsPopoverOpen(false); - if (onCreateDataView) { - onCreateDataView(); - } else { - openCreateDataViewFlyout(); - } - }, [onCreateDataView, openCreateDataViewFlyout]); - - const triggerLabel = currentSavedSearch?.title - ? currentSavedSearch.title - : currentDataView?.getName() ?? ''; - - const prepend = currentSavedSearch?.title - ? i18n.translate('xpack.ml.dataSourcePicker.discoverSessionLabel', { - defaultMessage: 'Discover session', - }) - : currentDataView - ? i18n.translate('xpack.ml.dataSourcePicker.dataViewLabel', { - defaultMessage: 'Data view', - }) - : undefined; - - const triggerButton = ( - setIsPopoverOpen(!isPopoverOpen)} - > - - - {triggerLabel || - i18n.translate('xpack.ml.dataSourcePicker.placeholderLabel', { - defaultMessage: 'Select data source', - })} - - - - ); - - const popover = ( - setIsPopoverOpen(false)} - panelPaddingSize="none" - panelProps={{ css: pickerPanelCss }} - aria-label={i18n.translate('xpack.ml.dataSourcePicker.popoverAriaLabel', { - defaultMessage: 'Data source selector', - })} - > - 'discoverApp', - name: i18n.translate('xpack.ml.dataSourcePicker.savedObjectType.discoverSession', { - defaultMessage: 'Discover session', - }), - showSavedObject: (savedObject: SavedObject) => - savedObject.attributes.isTextBasedQuery !== true, - }, - { - type: 'index-pattern', - getIconForSavedObject: () => 'indexPatternApp', - name: i18n.translate('xpack.ml.dataSourcePicker.savedObjectType.dataView', { - defaultMessage: 'Data view', - }), - ...(requireTimeBased - ? { - showSavedObject: (savedObject: SavedObject) => - !!savedObject.attributes.timeFieldName, - } - : {}), - }, - ]} - fixedPageSize={20} - services={{ - contentClient: contentManagement.client, - uiSettings, - }} - > - - - - - - ); - - if (prepend) { - return ( - - - - {popover} - - - - ); - } - - return popover; -}; diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx new file mode 100644 index 0000000000000..9c83c8d6d6947 --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx @@ -0,0 +1,136 @@ +/* + * 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, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { DataViewPicker } from '@kbn/unified-search-plugin/public'; + +import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; + +export interface MlDataSourcePickerProps { + currentDataView: DataView | null; + currentSavedSearch: SavedSearch | null; +} + +export const MlDataSourcePicker: FC = ({ currentDataView }) => { + const [savedDataViews, setSavedDataViews] = useState([]); + const [isOpenSessionPanelVisible, setOpenSessionPanelVisible] = useState(false); + const navigateToPath = useNavigateToPath(); + const location = useLocation(); + const { dataViews, dataViewEditor, dataViewFieldEditor, discover } = useMlKibana().services; + const closeFieldEditorRef = useRef<() => void | undefined>(); + + useEffect(() => { + return () => { + closeFieldEditorRef.current?.(); + }; + }, []); + useEffect(() => { + dataViews.getIdsWithTitle().then(setSavedDataViews); + }, [dataViews]); + + const onChangeDataView = useCallback( + (id: string) => { + navigateToPath(`${location.pathname}?index=${encodeURIComponent(id)}`); + }, + [navigateToPath, location.pathname] + ); + + const onDataViewCreated = useCallback( + (created: DataView) => { + if (created.id) { + dataViews.getIdsWithTitle().then(setSavedDataViews); + navigateToPath(`${location.pathname}?index=${encodeURIComponent(created.id)}`); + } + }, + [dataViews, navigateToPath, location.pathname] + ); + + const onOpenSavedSearch = useCallback( + (id: string) => { + setOpenSessionPanelVisible(false); + navigateToPath(`${location.pathname}?savedSearchId=${encodeURIComponent(id)}`); + }, + [navigateToPath, location.pathname] + ); + + const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + + const onAddField = useMemo( + () => + canEditDataView && currentDataView + ? async () => { + closeFieldEditorRef.current = await dataViewFieldEditor.openEditor({ + ctx: { dataView: currentDataView }, + onSave: () => { + dataViews.getIdsWithTitle().then(setSavedDataViews); + }, + }); + } + : undefined, + [canEditDataView, currentDataView, dataViewFieldEditor, dataViews] + ); + + const triggerLabel = + currentDataView?.getName() ?? + i18n.translate('xpack.ml.mlDataSourcePicker.selectDataViewLabel', { + defaultMessage: 'Select data view', + }); + + const { OpenSessionPanel } = discover; + + return ( + <> + + + + + + + setOpenSessionPanelVisible(true)} + data-test-subj="mlOpenDiscoverSessionButton" + aria-label={i18n.translate('xpack.ml.mlDataSourcePicker.openButton.ariaLabel', { + defaultMessage: 'Open Discover session', + })} + /> + + + + {isOpenSessionPanelVisible ? ( + setOpenSessionPanelVisible(false)} + onOpenSavedSearch={onOpenSavedSearch} + /> + ) : null} + + ); +}; diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts index af34723e9ac98..3d112343b4c81 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts @@ -30,6 +30,7 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; @@ -37,15 +38,18 @@ import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/publ import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; import type { CPSPluginStart } from '@kbn/cps/public/types'; +import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { MlServicesContext } from '../../app'; interface StartPlugins { cases?: CasesPublicStart; + discover: DiscoverStart; charts: ChartsPluginStart; contentManagement: ContentManagementPublicStart; dashboard: DashboardStart; data: DataPublicPluginStart; dataViewEditor: DataViewEditorStart; + dataViewFieldEditor: DataViewFieldEditorStart; dataViews: DataViewsPublicPluginStart; dataVisualizer?: DataVisualizerPluginStart; embeddable: EmbeddableStart; diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx index b32208b10e45c..a63f89c1fe2bc 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx @@ -11,13 +11,11 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DataDriftSpec } from '@kbn/data-visualizer-plugin/public'; -import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; -import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; +import { useMlKibana } from '../../contexts/kibana'; import { useDataSource } from '../../contexts/ml'; import { MlPageHeader } from '../../components/page_header'; import { PageTitle } from '../../components/page_title'; -import { DataSourcePicker } from '../../components/data_source_picker/data_source_picker'; -import { createPath } from '../../routing/router'; +import { MlDataSourcePicker } from '../../components/ml_data_source_picker/ml_data_source_picker'; export const DataDriftPage: FC = () => { const { @@ -34,14 +32,11 @@ export const DataDriftPage: FC = () => { }, [dataVisualizer]); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); - const navigateToPath = useNavigateToPath(); const dataSourcePicker = ( - navigateToPath(createPath(ML_PAGES.DATA_DRIFT_CUSTOM))} - createDataViewButtonTestSubj="dataDriftCreateDataViewButton" /> ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index e4e40511b09d2..d8e47a4d7d824 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { IndexDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public'; import { useTimefilter } from '@kbn/ml-date-picker'; -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import type { GetAdditionalLinksParams, @@ -29,7 +29,7 @@ import { useEnabledFeatures } from '../../contexts/ml'; import { useDataSource } from '../../contexts/ml/data_source_context'; import { useMlManagementLocator } from '../../contexts/kibana/use_create_url'; import { PageTitle } from '../../components/page_title'; -import { DataSourcePicker } from '../../components/data_source_picker/data_source_picker'; +import { MlDataSourcePicker } from '../../components/ml_data_source_picker/ml_data_source_picker'; export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); @@ -195,7 +195,10 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) ); const dataSourcePicker = !esql ? ( - + ) : undefined; return IndexDataVisualizer ? ( @@ -203,23 +206,21 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) {IndexDataVisualizer !== null ? ( <> - - - } - /> - {esql ? ( - <> - - - - - ) : null} - + ) : ( + + ) + } + /> {!dataView && !esql ? ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx index e80120452897d..204309cd1decc 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx @@ -26,7 +26,7 @@ export const DataVisualizerGrid: FC<{ isEsqlEnabled: boolean; cardTitleSize?: 's title={ } titleSize={cardTitleSize} @@ -87,14 +87,14 @@ export const DataVisualizerGrid: FC<{ isEsqlEnabled: boolean; cardTitleSize?: 's layout="horizontal" path={`/${ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER}`} title={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewTitle', { - defaultMessage: 'Data view', + defaultMessage: 'Index data visualizer', })} titleSize={cardTitleSize} - description={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewTitle', { + description={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewDescription', { defaultMessage: 'Analyze data, its shape, and statistical metadata from a data view.', })} buttonLabel={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewButtonLabel', { - defaultMessage: 'Select data view', + defaultMessage: 'Open Index data visualizer', })} cardDataTestSubj="mlDataVisualizerCardIndexData" buttonDataTestSubj="mlDataVisualizerSelectIndexButton" diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx index 4eff2daa27922..147f5ed309aa8 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx @@ -29,7 +29,7 @@ export const indexBasedRouteFactory = ( id: 'indexDataVisualizer', path: createPath(ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER), title: i18n.translate('xpack.ml.dataVisualizer.dataView.docTitle', { - defaultMessage: 'Index Data Visualizer', + defaultMessage: 'Index data visualizer', }), render: () => , breadcrumbs: [ @@ -37,7 +37,7 @@ export const indexBasedRouteFactory = ( getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), { text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataViewLabel', { - defaultMessage: 'Data View', + defaultMessage: 'Index data visualizer', }), }, ], @@ -50,7 +50,7 @@ export const indexESQLBasedRouteFactory = ( id: 'esqlDataVisualizer', path: createPath(ML_PAGES.DATA_VISUALIZER_ESQL), title: i18n.translate('xpack.ml.dataVisualizer.esql.docTitle', { - defaultMessage: 'Index Data Visualizer (ES|QL)', + defaultMessage: 'Index data visualizer (ES|QL)', }), render: () => , breadcrumbs: [ @@ -58,7 +58,7 @@ export const indexESQLBasedRouteFactory = ( getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), { text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.esqlLabel', { - defaultMessage: 'Index Data Visualizer (ES|QL)', + defaultMessage: 'Index data visualizer (ES|QL)', }), }, ], diff --git a/x-pack/platform/plugins/shared/ml/public/plugin.ts b/x-pack/platform/plugins/shared/ml/public/plugin.ts index a879aa0653303..c09055b18a6db 100644 --- a/x-pack/platform/plugins/shared/ml/public/plugin.ts +++ b/x-pack/platform/plugins/shared/ml/public/plugin.ts @@ -50,6 +50,7 @@ import type { CasesPublicSetup, CasesPublicStart } from '@kbn/cases-plugin/publi import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import { ENABLE_ESQL } from '@kbn/esql-utils'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; @@ -58,6 +59,7 @@ import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; import type { CPSPluginStart } from '@kbn/cps/public/types'; import { ProjectRoutingAccess } from '@kbn/cps-utils/types'; +import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { MlSharedServices } from './application/services/get_shared_ml_services'; import { getMlSharedServices } from './application/services/get_shared_ml_services'; import { registerManagementSections } from './application/management'; @@ -88,11 +90,13 @@ import { registerMlUiActions } from './ui_actions'; export interface MlStartDependencies { cases?: CasesPublicStart; + discover: DiscoverStart; charts: ChartsPluginStart; contentManagement: ContentManagementPublicStart; dashboard: DashboardStart; data: DataPublicPluginStart; dataViewEditor: DataViewEditorStart; + dataViewFieldEditor: DataViewFieldEditorStart; dataVisualizer: DataVisualizerPluginStart; embeddable: EmbeddableStart; fieldFormats: FieldFormatsRegistry; @@ -202,10 +206,12 @@ export class MlPlugin implements Plugin { { cases: pluginsStart.cases, charts: pluginsStart.charts, + discover: pluginsStart.discover, contentManagement: pluginsStart.contentManagement, dashboard: pluginsStart.dashboard, data: pluginsStart.data, dataViewEditor: pluginsStart.dataViewEditor, + dataViewFieldEditor: pluginsStart.dataViewFieldEditor, dataVisualizer: pluginsStart.dataVisualizer, embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, fieldFormats: pluginsStart.fieldFormats, diff --git a/x-pack/platform/plugins/shared/ml/tsconfig.json b/x-pack/platform/plugins/shared/ml/tsconfig.json index 0ce3eb6bb57e7..a8d4475f549a5 100644 --- a/x-pack/platform/plugins/shared/ml/tsconfig.json +++ b/x-pack/platform/plugins/shared/ml/tsconfig.json @@ -43,6 +43,7 @@ "@kbn/dashboard-plugin", "@kbn/data-plugin", "@kbn/data-view-editor-plugin", + "@kbn/data-view-field-editor-plugin", "@kbn/data-views-plugin", "@kbn/data-visualizer-plugin", "@kbn/discover-utils", From 628d5c6f21fb0919834482cf70fac4522b463fc6 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 10:08:39 +0200 Subject: [PATCH 06/32] fix(ml): add pageTitle prop to AIOps components - Updated ChangePointDetectionPage, LogCategorizationPage, and LogRateAnalysisPage to include a new `pageTitle` prop, enhancing the header content flexibility. - Modified MlDataSourcePicker to add a `title` attribute to the trigger for improved accessibility. --- .../ml/public/application/aiops/change_point_detection.tsx | 1 + .../shared/ml/public/application/aiops/log_categorization.tsx | 1 + .../shared/ml/public/application/aiops/log_rate_analysis.tsx | 1 + .../components/ml_data_source_picker/ml_data_source_picker.tsx | 1 + 4 files changed, 4 insertions(+) diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index d4bb9a47fb4ed..95b29dbcf97ae 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -84,6 +84,7 @@ export const ChangePointDetectionPage: FC = () => { dataView={dataView} savedSearch={savedSearch} showFrozenDataTierChoice={showNodeInfo} + pageTitle={pageTitle} headerContent={headerContent} appContextValue={{ embeddingOrigin: AIOPS_EMBEDDABLE_ORIGIN.ML_AIOPS_LABS, diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx index b1b3fb8979979..72f041818ccd2 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx @@ -74,6 +74,7 @@ export const LogCategorizationPage: FC = () => { dataView={dataView} savedSearch={savedSearch} showFrozenDataTierChoice={showNodeInfo} + pageTitle={pageTitle} headerContent={headerContent} appContextValue={{ embeddingOrigin: AIOPS_EMBEDDABLE_ORIGIN.ML_AIOPS_LABS, diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index 57f16f067bda5..0d23878344f3e 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -72,6 +72,7 @@ export const LogRateAnalysisPage: FC = () => { savedSearch={savedSearch} showContextualInsights={showContextualInsights} showFrozenDataTierChoice={showNodeInfo} + pageTitle={pageTitle} headerContent={headerContent} appContextValue={{ embeddingOrigin: AIOPS_EMBEDDABLE_ORIGIN.ML_AIOPS_LABS, diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx index 9c83c8d6d6947..ecede967fa13b 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx @@ -97,6 +97,7 @@ export const MlDataSourcePicker: FC = ({ currentDataVie savedDataViews={savedDataViews} trigger={{ label: triggerLabel, + title: triggerLabel, 'data-test-subj': 'mlDataSourceSelectorButton', }} onChangeDataView={onChangeDataView} From bc1ef262d304810262cc17d35b1d5d7d75aebb61 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 11:35:29 +0200 Subject: [PATCH 07/32] refactor(ml): move getMlNodeCount call into useEffect for data visualizer components - Updated FileDataVisualizerPage and IndexDataVisualizerPage to call getMlNodeCount within a useEffect hook, ensuring it runs only once on component mount. - This change improves performance and adheres to React best practices for managing side effects. --- .../datavisualizer/file_based/file_datavisualizer.tsx | 8 ++++++-- .../datavisualizer/index_based/index_data_visualizer.tsx | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index 0c2bc89faac14..762b5ff03c734 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -6,7 +6,7 @@ */ import type { FC } from 'react'; -import React, { useMemo, useCallback } from 'react'; +import React, { useEffect, useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useTimefilter } from '@kbn/ml-date-picker'; @@ -43,7 +43,11 @@ export const FileDataVisualizerPage: FC = () => { const mlApi = useMlApi(); const mlLocator = useMlLocator()!; const mlManagementLocator = useMlManagementLocatorInternal(); - getMlNodeCount(mlApi); + + useEffect(() => { + getMlNodeCount(mlApi); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const getDependencies = useCallback(async () => buildDependencies(services), [services]); diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index d8e47a4d7d824..1906fd7a7cc67 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -51,7 +51,11 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) const mlLocator = useMlLocator()!; const mlManagementLocator = useMlManagementLocator(); const mlFeaturesDisabled = !isFullLicense(); - getMlNodeCount(mlApi); + + useEffect(() => { + getMlNodeCount(mlApi); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const [IndexDataVisualizer, setIndexDataVisualizer] = useState( null From 75113abf67cde0bcb79c08e77b3d6aeab2f1eede Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 11:38:34 +0200 Subject: [PATCH 08/32] refactor(ml): add OpenSessionPanel mock and simplify MlDataSourcePicker usage - Introduced a mock implementation for OpenSessionPanel in the Discover plugin to enhance testing capabilities. - Simplified the usage of MlDataSourcePicker across various AIOps components by removing the currentSavedSearch prop, streamlining the component's interface and improving code clarity. --- src/platform/plugins/shared/discover/public/mocks.tsx | 1 + src/platform/plugins/shared/discover/public/plugin.tsx | 3 +-- .../public/application/aiops/change_point_detection.tsx | 7 +------ .../ml/public/application/aiops/log_categorization.tsx | 7 +------ .../ml/public/application/aiops/log_rate_analysis.tsx | 7 +------ .../ml_data_source_picker/ml_data_source_picker.tsx | 2 -- .../datavisualizer/data_drift/data_drift_page.tsx | 9 ++------- .../datavisualizer/index_based/index_data_visualizer.tsx | 7 ++----- 8 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/mocks.tsx b/src/platform/plugins/shared/discover/public/mocks.tsx index 8556cc62b4b63..a5f86de0f54cd 100644 --- a/src/platform/plugins/shared/discover/public/mocks.tsx +++ b/src/platform/plugins/shared/discover/public/mocks.tsx @@ -25,6 +25,7 @@ const createStartContract = (): Start => { const startContract: Start = { locator: sharePluginMock.createLocator(), DiscoverContainer: jest.fn().mockImplementation(() => <>), + OpenSessionPanel: jest.fn().mockImplementation(() => null), }; return startContract; }; diff --git a/src/platform/plugins/shared/discover/public/plugin.tsx b/src/platform/plugins/shared/discover/public/plugin.tsx index a6fcf221acf3d..4ff323a922ff1 100644 --- a/src/platform/plugins/shared/discover/public/plugin.tsx +++ b/src/platform/plugins/shared/discover/public/plugin.tsx @@ -67,7 +67,6 @@ import { forwardLegacyUrls } from './plugin_imports/forward_legacy_urls'; import { registerEsqlResultsAttachmentUi } from './agent_builder/register_esql_results_ui'; import { getProfilesInspectorView } from './context_awareness/inspector/get_profiles_inspector_view'; import { OpenSearchPanel } from './application/main/components/top_nav/open_search_panel'; -import type { DiscoverServices } from './build_services'; /** * Contains Discover, one of the oldest parts of Kibana @@ -319,7 +318,7 @@ export class DiscoverPlugin uiSettings: core.uiSettings, }; return ( - + ); diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index 95b29dbcf97ae..1e5db383e4c8e 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -38,12 +38,7 @@ export const ChangePointDetectionPage: FC = () => { /> ); - const headerContent = ( - - ); + const headerContent = ; return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx index 72f041818ccd2..ca158ae36f557 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx @@ -35,12 +35,7 @@ export const LogCategorizationPage: FC = () => { /> ); - const headerContent = ( - - ); + const headerContent = ; return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index 0d23878344f3e..c1b426d605cb4 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -32,12 +32,7 @@ export const LogRateAnalysisPage: FC = () => { ); - const headerContent = ( - - ); + const headerContent = ; return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx index ecede967fa13b..6bcb2d0408339 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx @@ -11,14 +11,12 @@ import { useLocation } from 'react-router-dom'; import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; export interface MlDataSourcePickerProps { currentDataView: DataView | null; - currentSavedSearch: SavedSearch | null; } export const MlDataSourcePicker: FC = ({ currentDataView }) => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx index a63f89c1fe2bc..2725aadbc48f8 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx @@ -33,12 +33,7 @@ export const DataDriftPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); - const dataSourcePicker = ( - - ); + const dataSourcePicker = ; return ( <> @@ -56,7 +51,7 @@ export const DataDriftPage: FC = () => { savedSearch={savedSearch} headerContent={dataSourcePicker} /> - ) : ( + ) : dataView ? null : ( <> {dataSourcePicker} = ({ esql = false }) } = useMlKibana(); const mlApi = useMlApi(); const { showNodeInfo } = useEnabledFeatures(); - const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const { selectedDataView: dataView } = useDataSource(); const mlLocator = useMlLocator()!; const mlManagementLocator = useMlManagementLocator(); const mlFeaturesDisabled = !isFullLicense(); @@ -199,10 +199,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) ); const dataSourcePicker = !esql ? ( - + ) : undefined; return IndexDataVisualizer ? ( From 523e7619000c0eb0a87b661848d291b55d738f6d Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 13:41:10 +0200 Subject: [PATCH 09/32] refactor(ml): replace OpenSessionPanel with ML specific implementation - Removed the OpenSessionPanel from the Discover plugin - Updated the DiscoverStart interface to reflect the removal of OpenSessionPanel, ensuring type consistency across the application. - Adjusted the MlDataSourcePicker to utilize the new MlOpenSessionFlyout component for session management, enhancing the user experience. --- .../plugins/shared/discover/public/mocks.tsx | 1 - .../plugins/shared/discover/public/plugin.tsx | 19 --- .../plugins/shared/discover/public/types.ts | 4 - .../shared/ml/public/application/app.tsx | 1 - .../ml_data_source_picker.tsx | 7 +- .../ml_open_session_flyout.tsx | 120 ++++++++++++++++++ .../contexts/kibana/kibana_context.ts | 2 - .../plugins/shared/ml/public/plugin.ts | 3 - 8 files changed, 123 insertions(+), 34 deletions(-) create mode 100644 x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx diff --git a/src/platform/plugins/shared/discover/public/mocks.tsx b/src/platform/plugins/shared/discover/public/mocks.tsx index a5f86de0f54cd..8556cc62b4b63 100644 --- a/src/platform/plugins/shared/discover/public/mocks.tsx +++ b/src/platform/plugins/shared/discover/public/mocks.tsx @@ -25,7 +25,6 @@ const createStartContract = (): Start => { const startContract: Start = { locator: sharePluginMock.createLocator(), DiscoverContainer: jest.fn().mockImplementation(() => <>), - OpenSessionPanel: jest.fn().mockImplementation(() => null), }; return startContract; }; diff --git a/src/platform/plugins/shared/discover/public/plugin.tsx b/src/platform/plugins/shared/discover/public/plugin.tsx index 4ff323a922ff1..a329770f60aff 100644 --- a/src/platform/plugins/shared/discover/public/plugin.tsx +++ b/src/platform/plugins/shared/discover/public/plugin.tsx @@ -29,7 +29,6 @@ import { ADD_PANEL_TRIGGER, ON_OPEN_PANEL_MENU } from '@kbn/ui-actions-plugin/co import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { ProjectRoutingAccess } from '@kbn/cps-utils'; import type { DiscoverSessionAttributes } from '@kbn/saved-search-plugin/server'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { DISCOVER_APP_LOCATOR, PLUGIN_ID, type DiscoverAppLocator } from '../common'; import { DISCOVER_CONTEXT_APP_LOCATOR, @@ -66,7 +65,6 @@ import type { ProfileProviderSharedServices, ProfilesManager } from './context_a import { forwardLegacyUrls } from './plugin_imports/forward_legacy_urls'; import { registerEsqlResultsAttachmentUi } from './agent_builder/register_esql_results_ui'; import { getProfilesInspectorView } from './context_awareness/inspector/get_profiles_inspector_view'; -import { OpenSearchPanel } from './application/main/components/top_nav/open_search_panel'; /** * Contains Discover, one of the oldest parts of Kibana @@ -306,23 +304,6 @@ export class DiscoverPlugin DiscoverContainer: (props: DiscoverContainerProps) => ( ), - OpenSessionPanel: (props: { - onClose: () => void; - onOpenSavedSearch: (id: string) => void; - }) => { - const openSessionServices = { - addBasePath: (path: string) => core.http.basePath.prepend(path), - capabilities: core.application.capabilities, - savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), - contentClient: plugins.contentManagement.client, - uiSettings: core.uiSettings, - }; - return ( - - - - ); - }, }; } diff --git a/src/platform/plugins/shared/discover/public/types.ts b/src/platform/plugins/shared/discover/public/types.ts index f91ebb649e95d..eb629c6d9770f 100644 --- a/src/platform/plugins/shared/discover/public/types.ts +++ b/src/platform/plugins/shared/discover/public/types.ts @@ -127,10 +127,6 @@ export interface DiscoverStart { * Use the Discover context awareness framework instead to register a custom Discover profile. */ readonly DiscoverContainer: ComponentType; - readonly OpenSessionPanel: ComponentType<{ - onClose: () => void; - onOpenSavedSearch: (id: string) => void; - }>; } /** diff --git a/x-pack/platform/plugins/shared/ml/public/application/app.tsx b/x-pack/platform/plugins/shared/ml/public/application/app.tsx index e9c8ca933e523..375327c0668fb 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/app.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/app.tsx @@ -82,7 +82,6 @@ export const App: FC = ({ ...coreStart, cases: deps.cases, charts: deps.charts, - discover: deps.discover, contentManagement: deps.contentManagement, dashboard: deps.dashboard, data: deps.data, diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx index 6bcb2d0408339..97d872ae41e05 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx @@ -14,6 +14,7 @@ import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; +import { MlOpenSessionFlyout } from './ml_open_session_flyout'; export interface MlDataSourcePickerProps { currentDataView: DataView | null; @@ -24,7 +25,7 @@ export const MlDataSourcePicker: FC = ({ currentDataVie const [isOpenSessionPanelVisible, setOpenSessionPanelVisible] = useState(false); const navigateToPath = useNavigateToPath(); const location = useLocation(); - const { dataViews, dataViewEditor, dataViewFieldEditor, discover } = useMlKibana().services; + const { dataViews, dataViewEditor, dataViewFieldEditor } = useMlKibana().services; const closeFieldEditorRef = useRef<() => void | undefined>(); useEffect(() => { @@ -84,8 +85,6 @@ export const MlDataSourcePicker: FC = ({ currentDataVie defaultMessage: 'Select data view', }); - const { OpenSessionPanel } = discover; - return ( <> @@ -125,7 +124,7 @@ export const MlDataSourcePicker: FC = ({ currentDataVie {isOpenSessionPanelVisible ? ( - setOpenSessionPanelVisible(false)} onOpenSavedSearch={onOpenSavedSearch} /> diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx new file mode 100644 index 0000000000000..18a25ce87777b --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx @@ -0,0 +1,120 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { hasActiveModifierKey } from '@kbn/shared-ux-utility'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { SavedSearchType, SavedSearchTypeDisplayName } from '@kbn/saved-search-plugin/common'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { useMlKibana } from '../../contexts/kibana'; + +interface MlOpenSessionFlyoutProps { + onClose: () => void; + onOpenSavedSearch: (id: string) => void; +} + +export const MlOpenSessionFlyout: FC = ({ + onClose, + onOpenSavedSearch, +}) => { + const modalTitleId = useGeneratedHtmlId(); + const { + services: { http, application, contentManagement, uiSettings }, + } = useMlKibana(); + + const hasSavedObjectPermission = + application.capabilities.savedObjectsManagement?.edit || + application.capabilities.savedObjectsManagement?.delete; + + return ( + + + +

+ +

+
+
+ + + } + savedObjectMetaData={[ + { + type: SavedSearchType, + getIconForSavedObject: () => 'discoverApp', + name: i18n.translate('xpack.ml.dataSourcePicker.openSessionFlyout.savedObjectName', { + defaultMessage: 'Discover session', + }), + }, + ]} + onChoose={(id) => { + onOpenSavedSearch(id); + onClose(); + }} + showFilter + /> + + {hasSavedObjectPermission && ( + + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + { + if (hasActiveModifierKey(e)) return; + onClose(); + }} + data-test-subj="manageSearchesBtn" + href={http.basePath.prepend( + `/app/management/kibana/objects?initialQuery=type:("${SavedSearchTypeDisplayName}")` + )} + > + + + + + + )} +
+ ); +}; diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts index 3d112343b4c81..1d35a8dc8db8c 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts @@ -38,12 +38,10 @@ import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/publ import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; import type { CPSPluginStart } from '@kbn/cps/public/types'; -import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { MlServicesContext } from '../../app'; interface StartPlugins { cases?: CasesPublicStart; - discover: DiscoverStart; charts: ChartsPluginStart; contentManagement: ContentManagementPublicStart; dashboard: DashboardStart; diff --git a/x-pack/platform/plugins/shared/ml/public/plugin.ts b/x-pack/platform/plugins/shared/ml/public/plugin.ts index c09055b18a6db..230607d5620c4 100644 --- a/x-pack/platform/plugins/shared/ml/public/plugin.ts +++ b/x-pack/platform/plugins/shared/ml/public/plugin.ts @@ -59,7 +59,6 @@ import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; import type { CPSPluginStart } from '@kbn/cps/public/types'; import { ProjectRoutingAccess } from '@kbn/cps-utils/types'; -import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { MlSharedServices } from './application/services/get_shared_ml_services'; import { getMlSharedServices } from './application/services/get_shared_ml_services'; import { registerManagementSections } from './application/management'; @@ -90,7 +89,6 @@ import { registerMlUiActions } from './ui_actions'; export interface MlStartDependencies { cases?: CasesPublicStart; - discover: DiscoverStart; charts: ChartsPluginStart; contentManagement: ContentManagementPublicStart; dashboard: DashboardStart; @@ -206,7 +204,6 @@ export class MlPlugin implements Plugin { { cases: pluginsStart.cases, charts: pluginsStart.charts, - discover: pluginsStart.discover, contentManagement: pluginsStart.contentManagement, dashboard: pluginsStart.dashboard, data: pluginsStart.data, From c5c9fed791f6f01393f6c9e62e0cc3aa62ea8c8e Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 15:19:08 +0200 Subject: [PATCH 10/32] refactor(ml): move MlDataSourcePicker and MlOpenSessionFlyout components --- .../private/ml/aiops_components/index.ts | 10 +++++ .../src/ml_data_source_picker/index.ts | 19 ++++++++ .../ml_data_source_picker.tsx | 44 ++++++++++++++++--- .../ml_open_session_flyout.tsx | 32 ++++++++++---- .../private/ml/aiops_components/tsconfig.json | 6 +++ .../aiops/change_point_detection.tsx | 17 +++++-- .../application/aiops/log_categorization.tsx | 17 +++++-- .../application/aiops/log_rate_analysis.tsx | 17 +++++-- .../data_drift/data_drift_page.tsx | 22 +++++++--- .../index_based/index_data_visualizer.tsx | 34 ++++++++------ .../platform/plugins/shared/ml/tsconfig.json | 1 + 11 files changed, 176 insertions(+), 43 deletions(-) create mode 100644 x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/index.ts rename x-pack/platform/{plugins/shared/ml/public/application/components => packages/private/ml/aiops_components/src}/ml_data_source_picker/ml_data_source_picker.tsx (78%) rename x-pack/platform/{plugins/shared/ml/public/application/components => packages/private/ml/aiops_components/src}/ml_data_source_picker/ml_open_session_flyout.tsx (79%) diff --git a/x-pack/platform/packages/private/ml/aiops_components/index.ts b/x-pack/platform/packages/private/ml/aiops_components/index.ts index c5798bc4b0ae0..1a59a49a597f7 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/index.ts +++ b/x-pack/platform/packages/private/ml/aiops_components/index.ts @@ -13,3 +13,13 @@ export { type BrushSettings, } from './src/document_count_chart'; export type { DocumentCountChartProps } from './src/document_count_chart'; +export { + MlDataSourcePicker, + MlOpenSessionFlyout, + type MlDataSourcePickerProps, + type MlDataSourcePickerServices, + type MlOpenSessionFlyoutProps, + type MlOpenSessionFlyoutServices, + type DataViewPickerProps, + type SavedObjectFinderProps, +} from './src/ml_data_source_picker'; diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/index.ts b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/index.ts new file mode 100644 index 0000000000000..275eddca92e1d --- /dev/null +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { + MlDataSourcePicker, + type MlDataSourcePickerProps, + type MlDataSourcePickerServices, + type DataViewPickerProps, +} from './ml_data_source_picker'; +export { + MlOpenSessionFlyout, + type MlOpenSessionFlyoutProps, + type MlOpenSessionFlyoutServices, + type SavedObjectFinderProps, +} from './ml_open_session_flyout'; diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx similarity index 78% rename from x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx rename to x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index 97d872ae41e05..a115b9d0a35fa 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -5,27 +5,55 @@ * 2.0. */ -import type { FC } from 'react'; +import type { ComponentType, FC } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; -import { DataViewPicker } from '@kbn/unified-search-plugin/public'; +import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; -import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; +import type { + MlOpenSessionFlyoutProps, + MlOpenSessionFlyoutServices, +} from './ml_open_session_flyout'; import { MlOpenSessionFlyout } from './ml_open_session_flyout'; +export type { DataViewPickerProps }; + +export interface MlDataSourcePickerServices extends MlOpenSessionFlyoutServices { + dataViews: { + getIdsWithTitle(): Promise; + }; + dataViewEditor?: { + userPermissions: { + editDataView(): boolean; + }; + }; + dataViewFieldEditor: { + openEditor(options: { ctx: { dataView: DataView }; onSave?: () => void }): Promise<() => void>; + }; +} + export interface MlDataSourcePickerProps { currentDataView: DataView | null; + services: MlDataSourcePickerServices; + navigateToPath: (path: string) => void | Promise; + DataViewPickerComponent: ComponentType; + SavedObjectFinderComponent: MlOpenSessionFlyoutProps['SavedObjectFinderComponent']; } -export const MlDataSourcePicker: FC = ({ currentDataView }) => { +export const MlDataSourcePicker: FC = ({ + currentDataView, + services, + navigateToPath, + DataViewPickerComponent, + SavedObjectFinderComponent, +}) => { const [savedDataViews, setSavedDataViews] = useState([]); const [isOpenSessionPanelVisible, setOpenSessionPanelVisible] = useState(false); - const navigateToPath = useNavigateToPath(); const location = useLocation(); - const { dataViews, dataViewEditor, dataViewFieldEditor } = useMlKibana().services; + const { dataViews, dataViewEditor, dataViewFieldEditor } = services; const closeFieldEditorRef = useRef<() => void | undefined>(); useEffect(() => { @@ -89,7 +117,7 @@ export const MlDataSourcePicker: FC = ({ currentDataVie <> - = ({ currentDataVie {isOpenSessionPanelVisible ? ( setOpenSessionPanelVisible(false)} onOpenSavedSearch={onOpenSavedSearch} + SavedObjectFinderComponent={SavedObjectFinderComponent} /> ) : null} diff --git a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx similarity index 79% rename from x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx rename to x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx index 18a25ce87777b..83d4378fa4c93 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/components/ml_data_source_picker/ml_open_session_flyout.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { ComponentType, FC } from 'react'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -22,22 +22,38 @@ import { useGeneratedHtmlId, } from '@elastic/eui'; import { SavedSearchType, SavedSearchTypeDisplayName } from '@kbn/saved-search-plugin/common'; -import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; -import { useMlKibana } from '../../contexts/kibana'; +import type { ApplicationStart, IUiSettingsClient } from '@kbn/core/public'; +import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import type { SavedObjectFinderProps } from '@kbn/saved-objects-finder-plugin/public'; -interface MlOpenSessionFlyoutProps { +export type { SavedObjectFinderProps }; + +export interface MlOpenSessionFlyoutServices { + http: { + basePath: { + prepend(path: string): string; + }; + }; + application: Pick; + contentManagement: ContentManagementPublicStart; + uiSettings: IUiSettingsClient; +} + +export interface MlOpenSessionFlyoutProps { + services: MlOpenSessionFlyoutServices; onClose: () => void; onOpenSavedSearch: (id: string) => void; + SavedObjectFinderComponent: ComponentType; } export const MlOpenSessionFlyout: FC = ({ + services, onClose, onOpenSavedSearch, + SavedObjectFinderComponent, }) => { const modalTitleId = useGeneratedHtmlId(); - const { - services: { http, application, contentManagement, uiSettings }, - } = useMlKibana(); + const { http, application, contentManagement, uiSettings } = services; const hasSavedObjectPermission = application.capabilities.savedObjectsManagement?.edit || @@ -61,7 +77,7 @@ export const MlOpenSessionFlyout: FC = ({ - { const { services } = useMlKibana(); + const navigateToPath = useNavigateToPath(); const { showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); @@ -38,7 +41,15 @@ export const ChangePointDetectionPage: FC = () => { /> ); - const headerContent = ; + const headerContent = ( + + ); return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx index ca158ae36f557..75c2189f9c9f6 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx @@ -14,16 +14,19 @@ import { LogCategorization } from '@kbn/aiops-plugin/public'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import { EuiEmptyPrompt } from '@elastic/eui'; +import { MlDataSourcePicker } from '@kbn/aiops-components'; +import { DataViewPicker } from '@kbn/unified-search-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useDataSource } from '../contexts/ml/data_source_context'; -import { useMlKibana } from '../contexts/kibana'; +import { useMlKibana, useNavigateToPath } from '../contexts/kibana'; import { useEnabledFeatures } from '../contexts/ml'; import { HelpMenu } from '../components/help_menu'; import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; -import { MlDataSourcePicker } from '../components/ml_data_source_picker/ml_data_source_picker'; export const LogCategorizationPage: FC = () => { const { services } = useMlKibana(); + const navigateToPath = useNavigateToPath(); const { showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); @@ -35,7 +38,15 @@ export const LogCategorizationPage: FC = () => { /> ); - const headerContent = ; + const headerContent = ( + + ); return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index c1b426d605cb4..ea10078956f06 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -14,16 +14,19 @@ import { LogRateAnalysis } from '@kbn/aiops-plugin/public'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import { EuiEmptyPrompt } from '@elastic/eui'; +import { MlDataSourcePicker } from '@kbn/aiops-components'; +import { DataViewPicker } from '@kbn/unified-search-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useDataSource } from '../contexts/ml/data_source_context'; -import { useMlKibana } from '../contexts/kibana'; +import { useMlKibana, useNavigateToPath } from '../contexts/kibana'; import { HelpMenu } from '../components/help_menu'; import { useEnabledFeatures } from '../contexts/ml'; import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; -import { MlDataSourcePicker } from '../components/ml_data_source_picker/ml_data_source_picker'; export const LogRateAnalysisPage: FC = () => { const { services } = useMlKibana(); + const navigateToPath = useNavigateToPath(); const { showContextualInsights, showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); @@ -32,7 +35,15 @@ export const LogRateAnalysisPage: FC = () => { ); - const headerContent = ; + const headerContent = ( + + ); return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx index 2725aadbc48f8..a3f54105fa93b 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx @@ -11,16 +11,18 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DataDriftSpec } from '@kbn/data-visualizer-plugin/public'; -import { useMlKibana } from '../../contexts/kibana'; +import { MlDataSourcePicker } from '@kbn/aiops-components'; +import { DataViewPicker } from '@kbn/unified-search-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; import { useDataSource } from '../../contexts/ml'; import { MlPageHeader } from '../../components/page_header'; import { PageTitle } from '../../components/page_title'; -import { MlDataSourcePicker } from '../../components/ml_data_source_picker/ml_data_source_picker'; export const DataDriftPage: FC = () => { - const { - services: { dataVisualizer }, - } = useMlKibana(); + const { services } = useMlKibana(); + const { dataVisualizer } = services; + const navigateToPath = useNavigateToPath(); const [DataDriftView, setDataDriftView] = useState(null); @@ -33,7 +35,15 @@ export const DataDriftPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); - const dataSourcePicker = ; + const dataSourcePicker = ( + + ); return ( <> diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index ce30f0f3eb266..43986d9c400e4 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -19,7 +19,10 @@ import type { GetAdditionalLinks, } from '@kbn/file-upload-common'; import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; -import { useMlApi, useMlKibana, useMlLocator } from '../../contexts/kibana'; +import { MlDataSourcePicker } from '@kbn/aiops-components'; +import { DataViewPicker } from '@kbn/unified-search-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { useMlApi, useMlKibana, useMlLocator, useNavigateToPath } from '../../contexts/kibana'; import { HelpMenu } from '../../components/help_menu'; import { isFullLicense } from '../../license'; import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes'; @@ -29,22 +32,21 @@ import { useEnabledFeatures } from '../../contexts/ml'; import { useDataSource } from '../../contexts/ml/data_source_context'; import { useMlManagementLocator } from '../../contexts/kibana/use_create_url'; import { PageTitle } from '../../components/page_title'; -import { MlDataSourcePicker } from '../../components/ml_data_source_picker/ml_data_source_picker'; export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); + const { services } = useMlKibana(); const { - services: { - docLinks, - dataVisualizer, - data: { - dataViews: { get: getDataView }, - }, - mlServices: { - mlApi: { recognizeIndex }, - }, + docLinks, + dataVisualizer, + data: { + dataViews: { get: getDataView }, + }, + mlServices: { + mlApi: { recognizeIndex }, }, - } = useMlKibana(); + } = services; + const navigateToPath = useNavigateToPath(); const mlApi = useMlApi(); const { showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView } = useDataSource(); @@ -199,7 +201,13 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) ); const dataSourcePicker = !esql ? ( - + ) : undefined; return IndexDataVisualizer ? ( diff --git a/x-pack/platform/plugins/shared/ml/tsconfig.json b/x-pack/platform/plugins/shared/ml/tsconfig.json index a8d4475f549a5..ce90fbdfdb461 100644 --- a/x-pack/platform/plugins/shared/ml/tsconfig.json +++ b/x-pack/platform/plugins/shared/ml/tsconfig.json @@ -23,6 +23,7 @@ "@kbn/actions-plugin", "@kbn/aiops-change-point-detection", "@kbn/aiops-common", + "@kbn/aiops-components", "@kbn/aiops-plugin", "@kbn/alerting-plugin", "@kbn/alerts-as-data-utils", From e45e78b5e1aa9b485f9db5dd506190cb5487bea3 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 17:01:54 +0200 Subject: [PATCH 11/32] refactor(ml): remove indexDataVisualizerPage references and clean up routing - Removed the indexDataVisualizerPage from various components and routing files to streamline navigation and reduce redundancy. - Updated related tests and navigation trees to reflect these changes, enhancing overall code clarity and maintainability. --- .../shared/deeplinks/ml/deep_links.ts | 1 - .../ml_data_source_picker.tsx | 1 + .../shared/ml/common-types/locator.ts | 1 - .../data_drift/data_drift_page.tsx | 9 +++++++- .../contexts/ml/data_source_context.tsx | 3 ++- .../index_based/index_data_visualizer.tsx | 3 +-- .../routing/routes/data_view_select.tsx | 21 ------------------- .../application/routing/routes/index.ts | 1 - .../routes/new_job/index_or_search.tsx | 2 -- .../ml/public/locator/ml_locator.test.ts | 12 ----------- .../search_deep_links.ts | 10 --------- .../observability/public/navigation_tree.ts | 4 ---- .../public/navigation_tree.ts | 4 ---- .../public/navigation_tree.ts | 1 - .../public/navigation_tree.ts | 1 - .../src/navigation_tree/ml_navigation_tree.ts | 4 ---- 16 files changed, 12 insertions(+), 66 deletions(-) delete mode 100644 x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx diff --git a/src/platform/packages/shared/deeplinks/ml/deep_links.ts b/src/platform/packages/shared/deeplinks/ml/deep_links.ts index c25865bf13de4..c416529e446d5 100644 --- a/src/platform/packages/shared/deeplinks/ml/deep_links.ts +++ b/src/platform/packages/shared/deeplinks/ml/deep_links.ts @@ -36,7 +36,6 @@ export type LinkId = | 'dataVisualizer' | 'fileUpload' | 'indexDataVisualizer' - | 'indexDataVisualizerPage' | 'settings' | 'calendarSettings' | 'calendarSettings' diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index a115b9d0a35fa..e9ba84e3e8852 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -61,6 +61,7 @@ export const MlDataSourcePicker: FC = ({ closeFieldEditorRef.current?.(); }; }, []); + useEffect(() => { dataViews.getIdsWithTitle().then(setSavedDataViews); }, [dataViews]); diff --git a/x-pack/platform/packages/shared/ml/common-types/locator.ts b/x-pack/platform/packages/shared/ml/common-types/locator.ts index 4d85575411211..cc329b354600c 100644 --- a/x-pack/platform/packages/shared/ml/common-types/locator.ts +++ b/x-pack/platform/packages/shared/ml/common-types/locator.ts @@ -72,7 +72,6 @@ export type MlGenericUrlState = MLPageState< | typeof ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION | typeof ML_PAGES.DATA_VISUALIZER | typeof ML_PAGES.DATA_VISUALIZER_FILE - | typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT | typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER | typeof ML_PAGES.DATA_VISUALIZER_ESQL | typeof ML_PAGES.FILTER_LISTS_MANAGE diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx index dfcb188fb1763..900a61c045387 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx @@ -19,6 +19,7 @@ import { EuiSpacer, EuiHorizontalRule, EuiBadge, + EuiTitle, } from '@elastic/eui'; import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; @@ -109,7 +110,13 @@ export const PageHeader: FC = ({ onRefresh, needsUpdate, header wrap={true} data-test-subj="dataComparisonTimeRangeSelectorSection" > - {headerContent ?? null} + + {headerContent ?? ( + +

{dataView.getName()}

+
+ )} +
{hasValidTimeField && ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx index 44675e6f4927f..d7ee0638b4783 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx @@ -83,7 +83,8 @@ export const DataSourceContextProvider: FC> = ({ chil } else if (dataViewId !== undefined) { dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId); } else { - dataViewAndSavedSearch.dataView = (await dataViews.getDefaultDataView().catch(() => null)) ?? null; + const defaultDataView = await dataViews.getDefaultDataView().catch(() => null); + dataViewAndSavedSearch.dataView = defaultDataView ?? null; } const { savedSearch, dataView } = dataViewAndSavedSearch; diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 43986d9c400e4..0d6d9b1e1bc3f 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -56,8 +56,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) useEffect(() => { getMlNodeCount(mlApi); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [mlApi]); const [IndexDataVisualizer, setIndexDataVisualizer] = useState( null diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx deleted file mode 100644 index fbc26a9be9bbf..0000000000000 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx +++ /dev/null @@ -1,21 +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 React from 'react'; -import { Redirect } from 'react-router-dom'; -import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; -import type { MlRoute } from '../router'; -import { createPath } from '../router'; - -export const dataVizIndexOrSearchRouteFactory = (): MlRoute => ({ - id: 'data_view_datavisualizer', - path: createPath(ML_PAGES.DATA_VISUALIZER_INDEX_SELECT), - render: ({ location }) => ( - - ), - breadcrumbs: [], -}); diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/index.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/index.ts index 9200173a81ced..e9020eedbe355 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/index.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/index.ts @@ -11,4 +11,3 @@ export * from './data_frame_analytics'; export * from './aiops'; export { timeSeriesExplorerRouteFactory } from './timeseriesexplorer'; export * from './explorer'; -export * from './data_view_select'; diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/new_job/index_or_search.tsx index 9f5c3ad98bfb9..31ceabf5a4059 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/new_job/index_or_search.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/new_job/index_or_search.tsx @@ -16,8 +16,6 @@ import { getMlManagementBreadcrumb, } from '../../breadcrumbs'; import { PageWrapper, MODE } from './index_or_search_page_wrapper'; -export { dataVizIndexOrSearchRouteFactory } from '../data_view_select'; - const getBreadcrumbs = (navigateToApp: NavigateToApp) => [ getStackManagementBreadcrumb(navigateToApp), getMlManagementBreadcrumb('ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB', navigateToApp), diff --git a/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts b/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts index b0e8e8ba1bf39..bb58e5f2d0cd6 100644 --- a/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts +++ b/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts @@ -181,18 +181,6 @@ describe('ML locator', () => { }); }); - it('should generate valid URL for the Index Data Visualizer select data view or saved search page', async () => { - const location = await definition.getLocation({ - page: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT, - }); - - expect(location).toMatchObject({ - app: 'ml', - path: '/datavisualizer_index_select', - state: {}, - }); - }); - it('should generate valid URL for the Index Data Visualizer Viewer page', async () => { const location = await definition.getLocation({ page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, diff --git a/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts b/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts index 52dda2e737bca..b3e6e8a176cb6 100644 --- a/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts +++ b/x-pack/platform/plugins/shared/ml/public/register_helper/register_search_links/search_deep_links.ts @@ -222,17 +222,7 @@ function createDeepLinks( title: i18n.translate('xpack.ml.deepLink.indexDataVisualizer', { defaultMessage: 'Index data visualizer', }), - path: `/${ML_PAGES.DATA_VISUALIZER_INDEX_SELECT}`, - }; - }, - getIndexDataVisualizerPageDeepLink: (): AppDeepLink => { - return { - id: 'indexDataVisualizerPage', - title: i18n.translate('xpack.ml.deepLink.indexDataVisualizer', { - defaultMessage: 'Index data visualizer', - }), path: `/${ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER}`, - visibleIn: [], }; }, diff --git a/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts b/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts index 2ce2f5cccadc0..e8b7a263238ba 100644 --- a/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts +++ b/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts @@ -310,10 +310,6 @@ function createNavTree({ link: 'ml:indexDataVisualizer', sideNavStatus: 'hidden', }, - { - link: 'ml:indexDataVisualizerPage', - sideNavStatus: 'hidden', - }, ], }, { diff --git a/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts b/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts index 0cc8b02be546a..4b82ca1b53102 100644 --- a/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts +++ b/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts @@ -288,10 +288,6 @@ export const createNavigationTree = ({ link: 'ml:indexDataVisualizer', sideNavStatus: 'hidden', }, - { - link: 'ml:indexDataVisualizerPage', - sideNavStatus: 'hidden', - }, ], }, { diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts index 6f039aa711ccf..902640d30ffc1 100644 --- a/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts +++ b/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts @@ -99,7 +99,6 @@ export const getNavigationTreeDefinition = ({ { link: 'ml:dataDriftPage', sideNavStatus: 'hidden' }, { link: 'ml:fileUpload', sideNavStatus: 'hidden' }, { link: 'ml:indexDataVisualizer', sideNavStatus: 'hidden' }, - { link: 'ml:indexDataVisualizerPage', sideNavStatus: 'hidden' }, ], id: 'ml_overview', title: '', diff --git a/x-pack/solutions/search/plugins/serverless_search/public/navigation_tree.ts b/x-pack/solutions/search/plugins/serverless_search/public/navigation_tree.ts index e098e5cbb4cdb..77277077ac9c9 100644 --- a/x-pack/solutions/search/plugins/serverless_search/public/navigation_tree.ts +++ b/x-pack/solutions/search/plugins/serverless_search/public/navigation_tree.ts @@ -105,7 +105,6 @@ export function createNavigationTree({ { link: 'ml:dataDriftPage', sideNavStatus: 'hidden' }, { link: 'ml:fileUpload', sideNavStatus: 'hidden' }, { link: 'ml:indexDataVisualizer', sideNavStatus: 'hidden' }, - { link: 'ml:indexDataVisualizerPage', sideNavStatus: 'hidden' }, ], }, { diff --git a/x-pack/solutions/security/packages/navigation/src/navigation_tree/ml_navigation_tree.ts b/x-pack/solutions/security/packages/navigation/src/navigation_tree/ml_navigation_tree.ts index 8f978de35e5dd..43166d46b4c57 100644 --- a/x-pack/solutions/security/packages/navigation/src/navigation_tree/ml_navigation_tree.ts +++ b/x-pack/solutions/security/packages/navigation/src/navigation_tree/ml_navigation_tree.ts @@ -42,10 +42,6 @@ export const createMachineLearningNavigationTree = (): NodeDefinition => ({ link: 'ml:indexDataVisualizer', sideNavStatus: 'hidden', }, - { - link: 'ml:indexDataVisualizerPage', - sideNavStatus: 'hidden', - }, ], }, { From bd83912735a2664f142f021556d42b599a1b1cd9 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 15 May 2026 17:29:16 +0200 Subject: [PATCH 12/32] test(ml): add unit tests for MlDataSourcePicker and DataSourceContextProvider components - Introduced comprehensive tests for the MlDataSourcePicker component, covering rendering, data view selection, and session management functionalities. - Added tests for the DataSourceContextProvider to ensure proper rendering based on URL parameters and error handling. - Enhanced overall test coverage for AIOps components, improving reliability and maintainability of the codebase. --- .../ml_data_source_picker.test.tsx | 217 ++++++++++++++++++ .../contexts/ml/data_source_context.test.tsx | 160 +++++++++++++ .../ml/public/locator/ml_locator.test.ts | 54 +++-- 3 files changed, 408 insertions(+), 23 deletions(-) create mode 100644 x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx create mode 100644 x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx new file mode 100644 index 0000000000000..856dabdfbb908 --- /dev/null +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { MlDataSourcePicker } from './ml_data_source_picker'; +import type { MlDataSourcePickerServices } from './ml_data_source_picker'; + +jest.mock('react-router-dom', () => ({ + useLocation: jest.fn(() => ({ pathname: '/jobs/new_job/step/data_view', search: '' })), +})); + +let capturedDataViewPickerProps: Record = {}; +const MockDataViewPicker = (props: any) => { + capturedDataViewPickerProps = props; + return ( +
+ {props.trigger?.label} +
+ ); +}; + +jest.mock('./ml_open_session_flyout', () => ({ + MlOpenSessionFlyout: (props: any) => { + return ( +
+ + +
+ ); + }, +})); + +const mockNavigateToPath = jest.fn(); +const mockGetIdsWithTitle = jest.fn().mockResolvedValue([]); +const mockOpenEditor = jest.fn().mockResolvedValue(() => {}); + +const buildServices = (overrides?: Partial): MlDataSourcePickerServices => + ({ + dataViews: { getIdsWithTitle: mockGetIdsWithTitle }, + dataViewEditor: { + userPermissions: { editDataView: jest.fn(() => true) }, + }, + dataViewFieldEditor: { openEditor: mockOpenEditor }, + http: { basePath: { prepend: jest.fn((p: string) => p) } }, + application: { capabilities: {} }, + contentManagement: { client: {} }, + uiSettings: {}, + ...overrides, + } as unknown as MlDataSourcePickerServices); + +const MockSavedObjectFinder = () =>
; + +const renderComponent = (props: { currentDataView: any; services?: MlDataSourcePickerServices }) => + render( + + + + ); + +describe('MlDataSourcePicker', () => { + beforeEach(() => { + jest.clearAllMocks(); + capturedDataViewPickerProps = {}; + }); + + it('renders DataViewPicker with "Select data view" label when currentDataView is null', async () => { + await act(async () => { + renderComponent({ currentDataView: null }); + }); + + expect(screen.getByTestId('mockDataViewPicker')).toBeDefined(); + expect(screen.getByText('Select data view')).toBeDefined(); + expect(capturedDataViewPickerProps.trigger?.label).toBe('Select data view'); + }); + + it('renders DataViewPicker with the data view name when currentDataView is provided', async () => { + const mockDataView = { + id: 'dv-1', + getName: jest.fn(() => 'My Data View'), + }; + + await act(async () => { + renderComponent({ currentDataView: mockDataView }); + }); + + expect(screen.getByText('My Data View')).toBeDefined(); + expect(capturedDataViewPickerProps.trigger?.label).toBe('My Data View'); + expect(capturedDataViewPickerProps.currentDataViewId).toBe('dv-1'); + }); + + it('calls navigateToPath with ?index= when onChangeDataView is triggered', async () => { + await act(async () => { + renderComponent({ currentDataView: null }); + }); + + await act(async () => { + capturedDataViewPickerProps.onChangeDataView('test-index-id'); + }); + + expect(mockNavigateToPath).toHaveBeenCalledWith( + '/jobs/new_job/step/data_view?index=test-index-id' + ); + }); + + it('renders MlOpenSessionFlyout when "Open Discover session" button is clicked', async () => { + await act(async () => { + renderComponent({ currentDataView: null }); + }); + + expect(screen.queryByTestId('mockOpenSessionFlyout')).toBeNull(); + + await act(async () => { + fireEvent.click(screen.getByTestId('mlOpenDiscoverSessionButton')); + }); + + expect(screen.getByTestId('mockOpenSessionFlyout')).toBeDefined(); + }); + + it('onOpenSavedSearch calls navigateToPath with ?savedSearchId= and hides the flyout', async () => { + await act(async () => { + renderComponent({ currentDataView: null }); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('mlOpenDiscoverSessionButton')); + }); + + expect(screen.getByTestId('mockOpenSessionFlyout')).toBeDefined(); + + await act(async () => { + fireEvent.click(screen.getByTestId('openSavedSearch')); + }); + + expect(mockNavigateToPath).toHaveBeenCalledWith( + '/jobs/new_job/step/data_view?savedSearchId=saved-search-id-1' + ); + expect(screen.queryByTestId('mockOpenSessionFlyout')).toBeNull(); + }); + + it('onDataViewCreated navigates and refreshes data views when the new view has an id', async () => { + const refreshedViews = [{ id: 'new-dv', title: 'New DV' }]; + mockGetIdsWithTitle.mockResolvedValueOnce([]).mockResolvedValueOnce(refreshedViews); + + await act(async () => { + renderComponent({ currentDataView: null }); + }); + + await act(async () => { + await capturedDataViewPickerProps.onDataViewCreated({ id: 'new-dv-id' }); + }); + + expect(mockNavigateToPath).toHaveBeenCalledWith( + '/jobs/new_job/step/data_view?index=new-dv-id' + ); + expect(mockGetIdsWithTitle).toHaveBeenCalledTimes(2); + }); + + it('onAddField is defined and calls dataViewFieldEditor.openEditor when canEditDataView=true and currentDataView is set', async () => { + const mockDataView = { + id: 'dv-1', + getName: jest.fn(() => 'My Data View'), + }; + + await act(async () => { + renderComponent({ currentDataView: mockDataView }); + }); + + expect(capturedDataViewPickerProps.onAddField).toBeDefined(); + + await act(async () => { + await capturedDataViewPickerProps.onAddField(); + }); + + expect(mockOpenEditor).toHaveBeenCalledWith({ + ctx: { dataView: mockDataView }, + onSave: expect.any(Function), + }); + }); + + it('onAddField is undefined when canEditDataView=false', async () => { + const mockDataView = { + id: 'dv-1', + getName: jest.fn(() => 'My Data View'), + }; + + const services = buildServices({ + dataViewEditor: { + userPermissions: { editDataView: jest.fn(() => false) }, + }, + } as any); + + await act(async () => { + renderComponent({ currentDataView: mockDataView, services }); + }); + + expect(capturedDataViewPickerProps.onAddField).toBeUndefined(); + }); +}); diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx new file mode 100644 index 0000000000000..395ca393ab3df --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { DataSourceContextProvider } from './data_source_context'; +import { useMlKibana } from '../kibana'; + +jest.mock('../kibana', () => ({ + useMlKibana: jest.fn(), +})); + +jest.mock('../../util/index_utils', () => ({ + getDataViewAndSavedSearchCallback: jest.fn(() => async (id: string) => ({ + dataView: { id, title: 'mock-saved-search-data-view' }, + savedSearch: { id: 'mock-saved-search' }, + })), +})); + +jest.mock('../../jobs/new_job/utils/new_job_utils', () => ({ + createSearchItems: jest.fn(() => ({ combinedQuery: { match_all: {} } })), +})); + +const mockLocationSearch = jest.fn(() => ''); +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ pathname: '/', search: mockLocationSearch() }), +})); + +const mockGet = jest.fn(); +const mockGetDefaultDataView = jest.fn(); +const mockGetDataViewAndSavedSearch = jest.fn(); + +const buildKibanaMock = () => ({ + services: { + data: { + dataViews: { + get: mockGet, + getDefaultDataView: mockGetDefaultDataView, + }, + }, + savedSearch: mockGetDataViewAndSavedSearch, + uiSettings: {}, + }, +}); + +const renderProvider = (children =
Hello
) => + render( + + {children} + + ); + +describe('DataSourceContextProvider', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useMlKibana as jest.Mock).mockReturnValue(buildKibanaMock()); + }); + + it('renders children when default data view is found (no URL params)', async () => { + mockLocationSearch.mockReturnValue(''); + mockGetDefaultDataView.mockResolvedValue({ + id: 'default-dv', + title: 'Default Data View', + }); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGetDefaultDataView).toHaveBeenCalled(); + }); + + it('renders children when index URL param is present', async () => { + mockLocationSearch.mockReturnValue('?index=my-index-id'); + mockGet.mockResolvedValue({ + id: 'my-index-id', + title: 'My Index', + }); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGet).toHaveBeenCalledWith('my-index-id'); + }); + + it('renders children when savedSearchId URL param is present', async () => { + const { getDataViewAndSavedSearchCallback } = jest.requireMock('../../util/index_utils'); + getDataViewAndSavedSearchCallback.mockReturnValue(async (id: string) => ({ + dataView: { id: 'dv-from-saved-search', title: 'From Saved Search' }, + savedSearch: { id }, + })); + + mockLocationSearch.mockReturnValue('?savedSearchId=my-saved-search'); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + }); + + it('shows error state when resolveDataSource throws', async () => { + mockLocationSearch.mockReturnValue('?index=bad-index'); + mockGet.mockRejectedValue(new Error('Data view not found')); + + renderProvider(); + + await waitFor(() => { + expect( + screen.getByText('Unable to fetch data view or saved Discover session') + ).toBeInTheDocument(); + }); + + expect(screen.queryByTestId('child-content')).not.toBeInTheDocument(); + }); + + it('renders null initially before the async resolve completes', async () => { + mockLocationSearch.mockReturnValue(''); + let resolvePromise: (value: any) => void; + mockGetDefaultDataView.mockReturnValue( + new Promise((resolve) => { + resolvePromise = resolve; + }) + ); + + const { container } = renderProvider(); + + expect(container.firstChild).toBeNull(); + + await waitFor(() => { + resolvePromise!({ id: 'default-dv', title: 'Default' }); + }); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + }); + + it('shows error when dataViewId is an empty string', async () => { + mockLocationSearch.mockReturnValue('?index='); + + renderProvider(); + + await waitFor(() => { + expect( + screen.getByText('Unable to fetch data view or saved Discover session') + ).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts b/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts index bb58e5f2d0cd6..5fb22616bb9a7 100644 --- a/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts +++ b/x-pack/platform/plugins/shared/ml/public/locator/ml_locator.test.ts @@ -204,30 +204,38 @@ describe('ML locator', () => { }); describe('AIOps labs', () => { - it('should throw an error for invalid Change point detection page state', async () => { - await expect( - definition.getLocation({ - page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, - pageState: { - index: '123123', - }, - }) - ).rejects.toThrow('Field configs are required to create a change point detection URL'); + it('should fall back to generic URL when only index is provided (no fieldConfigs)', async () => { + const location = await definition.getLocation({ + page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, + pageState: { + index: '123123', + }, + }); - await expect( - definition.getLocation({ - page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, - pageState: { - fieldConfigs: [ - { - fn: 'max', - metricField: 'CPUUtilization', - splitField: 'instance', - }, - ], - }, - }) - ).rejects.toThrow('Data view is required to create a change point detection URL'); + expect(location).toMatchObject({ + app: 'ml', + path: '/aiops/change_point_detection?index=123123', + state: {}, + }); + }); + + it('should fall back to generic URL when only fieldConfigs is provided (no index)', async () => { + const location = await definition.getLocation({ + page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, + pageState: { + fieldConfigs: [ + { + fn: 'max', + metricField: 'CPUUtilization', + splitField: 'instance', + }, + ], + }, + }); + + expect(location.app).toBe('ml'); + expect(location.path).toMatch(/^\/aiops\/change_point_detection/); + expect(location.state).toEqual({}); }); it('should generate valid URL for the Change point detection page', async () => { From 8ad94672cb5cece5ce23624fff3b3bed540523ad Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 May 2026 09:40:19 +0000 Subject: [PATCH 13/32] Changes from node scripts/regenerate_moon_projects.js --update --- .../platform/packages/private/ml/aiops_components/moon.yml | 6 ++++++ x-pack/platform/plugins/shared/ml/moon.yml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/x-pack/platform/packages/private/ml/aiops_components/moon.yml b/x-pack/platform/packages/private/ml/aiops_components/moon.yml index e0327bc000539..1f37649b2d0c1 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/moon.yml +++ b/x-pack/platform/packages/private/ml/aiops_components/moon.yml @@ -26,6 +26,12 @@ dependsOn: - '@kbn/visualization-utils' - '@kbn/aiops-log-rate-analysis' - '@kbn/ml-agg-utils' + - '@kbn/content-management-plugin' + - '@kbn/data-views-plugin' + - '@kbn/saved-objects-finder-plugin' + - '@kbn/saved-search-plugin' + - '@kbn/shared-ux-utility' + - '@kbn/unified-search-plugin' tags: - shared-common - package diff --git a/x-pack/platform/plugins/shared/ml/moon.yml b/x-pack/platform/plugins/shared/ml/moon.yml index bcf2280f542f6..1da4fdead5de8 100644 --- a/x-pack/platform/plugins/shared/ml/moon.yml +++ b/x-pack/platform/plugins/shared/ml/moon.yml @@ -21,6 +21,7 @@ dependsOn: - '@kbn/actions-plugin' - '@kbn/aiops-change-point-detection' - '@kbn/aiops-common' + - '@kbn/aiops-components' - '@kbn/aiops-plugin' - '@kbn/alerting-plugin' - '@kbn/alerts-as-data-utils' @@ -41,6 +42,7 @@ dependsOn: - '@kbn/dashboard-plugin' - '@kbn/data-plugin' - '@kbn/data-view-editor-plugin' + - '@kbn/data-view-field-editor-plugin' - '@kbn/data-views-plugin' - '@kbn/data-visualizer-plugin' - '@kbn/discover-utils' From d58adfe88eca3589ca94dd9ef6e4106bd85b3e71 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 May 2026 09:48:05 +0000 Subject: [PATCH 14/32] Changes from node scripts/eslint_all_files --no-cache --fix --- .../ml_data_source_picker/ml_data_source_picker.test.tsx | 8 ++++---- .../ml/public/application/overview/overview_ml_page.tsx | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx index 856dabdfbb908..b976ac13e55ba 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx @@ -47,7 +47,9 @@ const mockNavigateToPath = jest.fn(); const mockGetIdsWithTitle = jest.fn().mockResolvedValue([]); const mockOpenEditor = jest.fn().mockResolvedValue(() => {}); -const buildServices = (overrides?: Partial): MlDataSourcePickerServices => +const buildServices = ( + overrides?: Partial +): MlDataSourcePickerServices => ({ dataViews: { getIdsWithTitle: mockGetIdsWithTitle }, dataViewEditor: { @@ -168,9 +170,7 @@ describe('MlDataSourcePicker', () => { await capturedDataViewPickerProps.onDataViewCreated({ id: 'new-dv-id' }); }); - expect(mockNavigateToPath).toHaveBeenCalledWith( - '/jobs/new_job/step/data_view?index=new-dv-id' - ); + expect(mockNavigateToPath).toHaveBeenCalledWith('/jobs/new_job/step/data_view?index=new-dv-id'); expect(mockGetIdsWithTitle).toHaveBeenCalledTimes(2); }); diff --git a/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx index 9f425680b2f46..418fbe9a4dcb7 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/overview/overview_ml_page.tsx @@ -304,9 +304,7 @@ export const OverviewPage: FC = () => { - navigateToPath('/aiops/change_point_detection') - } + onClick={() => navigateToPath('/aiops/change_point_detection')} data-test-subj="mlOverviewCardChangePointDetectionButton" aria-label={i18n.translate( 'xpack.ml.overview.changePointDetection.findChangesButton', From 636c1fb7bcf7ba7b6c168ec90789dc3c4dedb979 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Tue, 19 May 2026 12:44:04 +0200 Subject: [PATCH 15/32] refactor(ml): update i18n keys for MlDataSourcePicker and MlOpenSessionFlyout components - Changed translation keys in MlDataSourcePicker and MlOpenSessionFlyout components to reflect the new namespace under 'xpack.aiops'. - Updated default messages in DataVisualizerGrid for improved clarity and consistency in user interface text. --- .../src/ml_data_source_picker/ml_data_source_picker.tsx | 6 +++--- .../src/ml_data_source_picker/ml_open_session_flyout.tsx | 8 ++++---- x-pack/platform/plugins/shared/aiops/kibana.jsonc | 3 ++- .../public/application/overview/data_visualizer_grid.tsx | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index e9ba84e3e8852..8e7881b4e1215 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -110,7 +110,7 @@ export const MlDataSourcePicker: FC = ({ const triggerLabel = currentDataView?.getName() ?? - i18n.translate('xpack.ml.mlDataSourcePicker.selectDataViewLabel', { + i18n.translate('xpack.aiops.mlDataSourcePicker.selectDataViewLabel', { defaultMessage: 'Select data view', }); @@ -134,7 +134,7 @@ export const MlDataSourcePicker: FC = ({ @@ -145,7 +145,7 @@ export const MlDataSourcePicker: FC = ({ color="text" onClick={() => setOpenSessionPanelVisible(true)} data-test-subj="mlOpenDiscoverSessionButton" - aria-label={i18n.translate('xpack.ml.mlDataSourcePicker.openButton.ariaLabel', { + aria-label={i18n.translate('xpack.aiops.mlDataSourcePicker.openButton.ariaLabel', { defaultMessage: 'Open Discover session', })} /> diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx index 83d4378fa4c93..83f46323d5468 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -70,7 +70,7 @@ export const MlOpenSessionFlyout: FC = ({

@@ -86,7 +86,7 @@ export const MlOpenSessionFlyout: FC = ({ }} noItemsMessage={ } @@ -94,7 +94,7 @@ export const MlOpenSessionFlyout: FC = ({ { type: SavedSearchType, getIconForSavedObject: () => 'discoverApp', - name: i18n.translate('xpack.ml.dataSourcePicker.openSessionFlyout.savedObjectName', { + name: i18n.translate('xpack.aiops.dataSourcePicker.openSessionFlyout.savedObjectName', { defaultMessage: 'Discover session', }), }, @@ -123,7 +123,7 @@ export const MlOpenSessionFlyout: FC = ({ )} >
diff --git a/x-pack/platform/plugins/shared/aiops/kibana.jsonc b/x-pack/platform/plugins/shared/aiops/kibana.jsonc index 2a9511ace6871..d6baa590ae6e4 100644 --- a/x-pack/platform/plugins/shared/aiops/kibana.jsonc +++ b/x-pack/platform/plugins/shared/aiops/kibana.jsonc @@ -35,7 +35,8 @@ "fieldFormats", "kibanaReact", "kibanaUtils", - "cases" + "cases", + "savedSearch" ] } } diff --git a/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx index 204309cd1decc..b4b23a4be3303 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx @@ -26,7 +26,7 @@ export const DataVisualizerGrid: FC<{ isEsqlEnabled: boolean; cardTitleSize?: 's title={ } titleSize={cardTitleSize} @@ -87,14 +87,14 @@ export const DataVisualizerGrid: FC<{ isEsqlEnabled: boolean; cardTitleSize?: 's layout="horizontal" path={`/${ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER}`} title={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewTitle', { - defaultMessage: 'Index data visualizer', + defaultMessage: 'Data view', })} titleSize={cardTitleSize} description={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewDescription', { defaultMessage: 'Analyze data, its shape, and statistical metadata from a data view.', })} buttonLabel={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewButtonLabel', { - defaultMessage: 'Open Index data visualizer', + defaultMessage: 'Open data visualizer', })} cardDataTestSubj="mlDataVisualizerCardIndexData" buttonDataTestSubj="mlDataVisualizerSelectIndexButton" From 30fc6836a92457b5ca5a07c5e1dd41dd7e456864 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 May 2026 11:10:48 +0000 Subject: [PATCH 16/32] Changes from node scripts/eslint_all_files --no-cache --fix --- .../src/ml_data_source_picker/ml_open_session_flyout.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx index 83f46323d5468..540edd7cc8587 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -94,9 +94,12 @@ export const MlOpenSessionFlyout: FC = ({ { type: SavedSearchType, getIconForSavedObject: () => 'discoverApp', - name: i18n.translate('xpack.aiops.dataSourcePicker.openSessionFlyout.savedObjectName', { - defaultMessage: 'Discover session', - }), + name: i18n.translate( + 'xpack.aiops.dataSourcePicker.openSessionFlyout.savedObjectName', + { + defaultMessage: 'Discover session', + } + ), }, ]} onChoose={(id) => { From 14e710e1f797aafb6f6daa274bc7fa0e377f4d87 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Tue, 19 May 2026 13:39:26 +0200 Subject: [PATCH 17/32] feat(ml): add filterEsql prop to MlDataSourcePicker and MlOpenSessionFlyout components - Introduced a new optional `filterEsql` prop to both MlDataSourcePicker and MlOpenSessionFlyout components to control the visibility of ES|QL-based sessions. - Updated the implementation to conditionally hide sessions based on the `filterEsql` value, enhancing user experience and flexibility in session management. - Adjusted the IndexDataVisualizerPage to utilize the new `filterEsql` prop when rendering components. --- .../ml_data_source_picker.tsx | 4 ++++ .../ml_open_session_flyout.tsx | 16 +++++++++++++++- .../index_based/index_data_visualizer.tsx | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index 8e7881b4e1215..711b8742b3c02 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -41,6 +41,8 @@ export interface MlDataSourcePickerProps { navigateToPath: (path: string) => void | Promise; DataViewPickerComponent: ComponentType; SavedObjectFinderComponent: MlOpenSessionFlyoutProps['SavedObjectFinderComponent']; + /** When true, ES|QL-based sessions are hidden from the session picker */ + filterEsql?: boolean; } export const MlDataSourcePicker: FC = ({ @@ -49,6 +51,7 @@ export const MlDataSourcePicker: FC = ({ navigateToPath, DataViewPickerComponent, SavedObjectFinderComponent, + filterEsql = false, }) => { const [savedDataViews, setSavedDataViews] = useState([]); const [isOpenSessionPanelVisible, setOpenSessionPanelVisible] = useState(false); @@ -158,6 +161,7 @@ export const MlDataSourcePicker: FC = ({ onClose={() => setOpenSessionPanelVisible(false)} onOpenSavedSearch={onOpenSavedSearch} SavedObjectFinderComponent={SavedObjectFinderComponent} + filterEsql={filterEsql} /> ) : null} diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx index 540edd7cc8587..59c95ab37a8e1 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -21,10 +21,15 @@ import { EuiTitle, useGeneratedHtmlId, } from '@elastic/eui'; -import { SavedSearchType, SavedSearchTypeDisplayName } from '@kbn/saved-search-plugin/common'; +import { + SavedSearchType, + SavedSearchTypeDisplayName, + type SavedSearchAttributes, +} from '@kbn/saved-search-plugin/common'; import type { ApplicationStart, IUiSettingsClient } from '@kbn/core/public'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import type { SavedObjectFinderProps } from '@kbn/saved-objects-finder-plugin/public'; +import type { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; export type { SavedObjectFinderProps }; @@ -44,6 +49,8 @@ export interface MlOpenSessionFlyoutProps { onClose: () => void; onOpenSavedSearch: (id: string) => void; SavedObjectFinderComponent: ComponentType; + /** When true, ES|QL-based sessions are hidden from the list */ + filterEsql?: boolean; } export const MlOpenSessionFlyout: FC = ({ @@ -51,6 +58,7 @@ export const MlOpenSessionFlyout: FC = ({ onClose, onOpenSavedSearch, SavedObjectFinderComponent, + filterEsql = false, }) => { const modalTitleId = useGeneratedHtmlId(); const { http, application, contentManagement, uiSettings } = services; @@ -100,6 +108,12 @@ export const MlOpenSessionFlyout: FC = ({ defaultMessage: 'Discover session', } ), + ...(filterEsql + ? { + showSavedObject: (savedObject: SavedObjectCommon) => + !savedObject.attributes.isTextBasedQuery, + } + : {}), }, ]} onChoose={(id) => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 0d6d9b1e1bc3f..198ddd8cd87ab 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -206,6 +206,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) navigateToPath={navigateToPath} DataViewPickerComponent={DataViewPicker} SavedObjectFinderComponent={SavedObjectFinder} + filterEsql /> ) : undefined; From 16873922ec90daa3047ba2cbf15be0cdf0babba5 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Wed, 20 May 2026 12:03:00 +0200 Subject: [PATCH 18/32] fix(ml): proper breadcrumbs and disableSearchSession in management context - Added a new optional `disableSearchSession` prop to the DataVisualizerStateContextProvider and IndexDataVisualizer components to control search session behavior. - Updated the IndexDataVisualizerPage to utilize the `disableSearchSession` prop based on the management context. - Refactored routing to support the new management routes for index-based data visualizer, improving navigation and user experience. --- .../index_data_visualizer.tsx | 9 +++- .../index_based/index_data_visualizer.tsx | 24 +++++++-- .../routes/anomaly_detection_management.ts | 9 +++- .../routing/routes/datavisualizer/index.ts | 2 +- .../routes/datavisualizer/index_based.tsx | 54 +++++++++++++++++-- 5 files changed, 87 insertions(+), 11 deletions(-) diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 10f2e68621e86..7e094e2549137 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -53,6 +53,7 @@ export interface DataVisualizerStateContextProviderProps { IndexDataVisualizerComponent: FC; getAdditionalLinks?: GetAdditionalLinks; headerContent?: ReactNode; + disableSearchSession?: boolean; } export type IndexDataVisualizerSpec = typeof IndexDataVisualizer; @@ -115,6 +116,7 @@ const DataVisualizerStateContextProvider: FC { const { services } = useDataVisualizerKibana(); const { @@ -132,6 +134,8 @@ const DataVisualizerStateContextProvider: FC(undefined); useEffect(() => { + if (disableSearchSession) return; + const urlState = parseUrlState(urlSearchString); if (search.session) { @@ -176,7 +180,7 @@ const DataVisualizerStateContextProvider: FC { const prevSearchString = urlSearchString; @@ -305,6 +309,7 @@ export interface Props { showFrozenDataTierChoice?: boolean; esql?: boolean; headerContent?: ReactNode; + disableSearchSession?: boolean; } export const IndexDataVisualizer: FC = ({ @@ -312,6 +317,7 @@ export const IndexDataVisualizer: FC = ({ showFrozenDataTierChoice = true, esql, headerContent, + disableSearchSession, }) => { const coreStart = getCoreStart(); const { @@ -365,6 +371,7 @@ export const IndexDataVisualizer: FC = ({ IndexDataVisualizerComponent={IndexDataVisualizerView} getAdditionalLinks={getAdditionalLinks} headerContent={headerContent} + disableSearchSession={disableSearchSession} /> ) : ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 198ddd8cd87ab..d4097ba503997 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -6,7 +6,7 @@ */ import type { FC } from 'react'; -import React, { Fragment, useEffect, useMemo, useState } from 'react'; +import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { IndexDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public'; @@ -33,7 +33,10 @@ import { useDataSource } from '../../contexts/ml/data_source_context'; import { useMlManagementLocator } from '../../contexts/kibana/use_create_url'; import { PageTitle } from '../../components/page_title'; -export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => { +export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: boolean }> = ({ + esql = false, + isManagementContext, +}) => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); const { services } = useMlKibana(); const { @@ -54,6 +57,20 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) const mlManagementLocator = useMlManagementLocator(); const mlFeaturesDisabled = !isFullLicense(); + const dataSourceNavigate = useCallback( + async (path: string) => { + if (isManagementContext && mlManagementLocator) { + await mlManagementLocator.navigate({ + sectionId: 'ml', + appId: `anomaly_detection${path}`, + }); + } else { + await navigateToPath(path); + } + }, + [isManagementContext, navigateToPath, mlManagementLocator] + ); + useEffect(() => { getMlNodeCount(mlApi); }, [mlApi]); @@ -203,7 +220,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) = ({ esql = false }) showFrozenDataTierChoice={showNodeInfo} esql={esql} headerContent={dataSourcePicker} + disableSearchSession={isManagementContext} /> )} diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts index 586d9da08dccb..214b74d0fb535 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts @@ -6,6 +6,13 @@ */ export * from './new_job'; -export * from './datavisualizer'; +export * from './datavisualizer/data_drift'; +export * from './datavisualizer/data_comparison'; +export * from './datavisualizer/datavisualizer'; +export * from './datavisualizer/file_based'; +export { + indexBasedManagementRouteFactory, + indexESQLBasedManagementRouteFactory, +} from './datavisualizer/index_based'; export * from './jobs_list'; export * from './supplied_configurations'; diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index.ts index 76b8fb5eef58c..5ba3512d9f992 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index.ts @@ -8,5 +8,5 @@ export * from './data_drift'; export * from './data_comparison'; export * from './datavisualizer'; -export * from './index_based'; +export { indexBasedRouteFactory, indexESQLBasedRouteFactory } from './index_based'; export * from './file_based'; diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx index 147f5ed309aa8..93aadb05b5c87 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx @@ -14,7 +14,12 @@ import type { NavigateToPath } from '../../../contexts/kibana'; import type { MlRoute } from '../../router'; import { createPath, PageLoader } from '../../router'; import { useRouteResolver } from '../../use_resolver'; -import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; +import type { NavigateToApp } from '../../breadcrumbs'; +import { + getBreadcrumbWithUrlForApp, + getStackManagementBreadcrumb, + getMlManagementBreadcrumb, +} from '../../breadcrumbs'; import { DataSourceContextProvider } from '../../../contexts/ml'; const Page = dynamic(async () => ({ @@ -31,7 +36,7 @@ export const indexBasedRouteFactory = ( title: i18n.translate('xpack.ml.dataVisualizer.dataView.docTitle', { defaultMessage: 'Index data visualizer', }), - render: () => , + render: () => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), @@ -52,7 +57,7 @@ export const indexESQLBasedRouteFactory = ( title: i18n.translate('xpack.ml.dataVisualizer.esql.docTitle', { defaultMessage: 'Index data visualizer (ES|QL)', }), - render: () => , + render: () => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), @@ -64,13 +69,52 @@ export const indexESQLBasedRouteFactory = ( ], }); -const PageWrapper: FC<{ esql: boolean }> = ({ esql }) => { +export const indexBasedManagementRouteFactory = (navigateToApp: NavigateToApp): MlRoute => ({ + id: 'indexDataVisualizer', + path: createPath(ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER), + title: i18n.translate('xpack.ml.dataVisualizer.dataView.docTitle', { + defaultMessage: 'Index data visualizer', + }), + render: () => , + breadcrumbs: [ + getStackManagementBreadcrumb(navigateToApp), + getMlManagementBreadcrumb('ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB', navigateToApp), + { + text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataViewLabel', { + defaultMessage: 'Index data visualizer', + }), + }, + ], +}); + +export const indexESQLBasedManagementRouteFactory = (navigateToApp: NavigateToApp): MlRoute => ({ + id: 'esqlDataVisualizer', + path: createPath(ML_PAGES.DATA_VISUALIZER_ESQL), + title: i18n.translate('xpack.ml.dataVisualizer.esql.docTitle', { + defaultMessage: 'Index data visualizer (ES|QL)', + }), + render: () => , + breadcrumbs: [ + getStackManagementBreadcrumb(navigateToApp), + getMlManagementBreadcrumb('ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB', navigateToApp), + { + text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.esqlLabel', { + defaultMessage: 'Index data visualizer (ES|QL)', + }), + }, + ], +}); + +const PageWrapper: FC<{ esql: boolean; isManagementContext: boolean }> = ({ + esql, + isManagementContext, +}) => { const { context } = useRouteResolver('basic', []); return ( - + ); From bd650e06809a3b904ba307fa3a008b10278a73a0 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Wed, 20 May 2026 14:53:03 +0200 Subject: [PATCH 19/32] refactor(ml): optimize MlDataSourcePicker and IndexDataVisualizerPage components - Refactored the MlDataSourcePicker to use useMemo for performance improvements in canEditDataView and triggerLabel calculations. - Simplified the rendering logic in MlDataSourcePicker to enhance readability and maintainability. - Updated IndexDataVisualizerPage to manage URL parameters for data view selection, ensuring consistent behavior when no index is specified. - Added a horizontal rule in the IndexDataVisualizerPage for better visual separation in management context. --- .../ml_data_source_picker.tsx | 116 ++++++++++-------- .../index_based/index_data_visualizer.tsx | 3 +- 2 files changed, 67 insertions(+), 52 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index 711b8742b3c02..feeeab28d8fbd 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -94,7 +94,10 @@ export const MlDataSourcePicker: FC = ({ [navigateToPath, location.pathname] ); - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = useMemo( + () => Boolean(dataViewEditor?.userPermissions.editDataView()), + [dataViewEditor] + ); const onAddField = useMemo( () => @@ -111,59 +114,70 @@ export const MlDataSourcePicker: FC = ({ [canEditDataView, currentDataView, dataViewFieldEditor, dataViews] ); - const triggerLabel = - currentDataView?.getName() ?? - i18n.translate('xpack.aiops.mlDataSourcePicker.selectDataViewLabel', { - defaultMessage: 'Select data view', - }); + const triggerLabel = useMemo( + () => + currentDataView?.getName() ?? + i18n.translate('xpack.aiops.mlDataSourcePicker.selectDataViewLabel', { + defaultMessage: 'Select data view', + }), + [currentDataView] + ); - return ( - <> - - - - - - setOpenSessionPanelVisible(true), []); + + const onCloseSession = useCallback(() => setOpenSessionPanelVisible(false), []); + + const dataViewPickerContent = ( + + + + + + + - setOpenSessionPanelVisible(true)} - data-test-subj="mlOpenDiscoverSessionButton" - aria-label={i18n.translate('xpack.aiops.mlDataSourcePicker.openButton.ariaLabel', { - defaultMessage: 'Open Discover session', - })} - /> - - - - {isOpenSessionPanelVisible ? ( - setOpenSessionPanelVisible(false)} - onOpenSavedSearch={onOpenSavedSearch} - SavedObjectFinderComponent={SavedObjectFinderComponent} - filterEsql={filterEsql} - /> - ) : null} + /> + + + + ); + + return isOpenSessionPanelVisible ? ( + <> + {dataViewPickerContent} + + ) : ( + dataViewPickerContent ); }; diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index d4097ba503997..a6e7cb7324364 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { IndexDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public'; import { useTimefilter } from '@kbn/ml-date-picker'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiHorizontalRule } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import type { GetAdditionalLinksParams, @@ -248,6 +248,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: b } /> + {isManagementContext && } {!dataView && !esql ? ( <> {dataSourcePicker} From f6fa32bfe1af0c384bac32db13d5c22899674062 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Thu, 21 May 2026 16:12:25 +0200 Subject: [PATCH 20/32] refactor(ml): enhance data view selection and update test cases - Updated ComboBoxService to include both `.euiComboBoxOption` and `.euiFilterSelectItem` in option retrieval, improving flexibility in option selection. - Added `onFieldSaved` callback to MlDataSourcePicker for better handling of field save events. - Modified index data visualizer tests to accommodate new parameters for source selection, ensuring accurate test coverage for both data views and saved searches. - Adjusted various service files to replace outdated selectors with the new `mlDataSourceSelectorButton`, enhancing consistency across the application. --- .../test/functional/services/combo_box.ts | 21 ++++-- .../ml_data_source_picker.tsx | 6 +- .../index_data_visualizer.tsx | 1 + .../index_based/index_data_visualizer.tsx | 3 +- .../group1/index_data_visualizer.ts | 6 +- .../group1/index_data_visualizer_filters.ts | 6 +- .../index_data_visualizer_random_sampler.ts | 18 +++-- .../ml/data_visualizer/group2/data_drift.ts | 3 +- .../index_data_visualizer_actions_panel.ts | 2 +- ...ex_data_visualizer_data_view_management.ts | 12 ++++ .../aiops/change_point_detection_page.ts | 2 +- .../aiops/log_pattern_analysis_page.ts | 2 +- .../services/aiops/log_rate_analysis_page.ts | 2 +- .../test/functional/services/ml/data_drift.ts | 10 +-- .../functional/services/ml/data_visualizer.ts | 2 +- ...ata_visualizer_index_pattern_management.ts | 38 +---------- .../services/ml/job_source_selection.ts | 67 +++++++++++++++++-- 17 files changed, 130 insertions(+), 71 deletions(-) diff --git a/src/platform/test/functional/services/combo_box.ts b/src/platform/test/functional/services/combo_box.ts index b9cd3cd5e63a2..981d84f5d9e5e 100644 --- a/src/platform/test/functional/services/combo_box.ts +++ b/src/platform/test/functional/services/combo_box.ts @@ -87,7 +87,10 @@ export class ComboBoxService extends FtrService { public async getOptions(comboBoxSelector: string) { const comboBoxElement = await this.testSubjects.find(comboBoxSelector); await this.openOptionsList(comboBoxElement); - return await this.find.allByCssSelector('.euiComboBoxOption', this.WAIT_FOR_EXISTS_TIME); + return await this.find.allByCssSelector( + '.euiComboBoxOption, .euiFilterSelectItem', + this.WAIT_FOR_EXISTS_TIME + ); } /** @@ -116,7 +119,7 @@ export class ComboBoxService extends FtrService { if (trimmedValue !== undefined) { const selectOptions = await this.find.allByCssSelector( - `.euiComboBoxOption[title="${trimmedValue}"]`, + `.euiComboBoxOption[title="${trimmedValue}"], .euiFilterSelectItem[title="${trimmedValue}"]`, this.WAIT_FOR_EXISTS_TIME ); @@ -127,7 +130,10 @@ export class ComboBoxService extends FtrService { const alternateTitle = ( await Promise.all( ( - await this.find.allByCssSelector(`.euiComboBoxOption`, this.WAIT_FOR_EXISTS_TIME) + await this.find.allByCssSelector( + `.euiComboBoxOption, .euiFilterSelectItem`, + this.WAIT_FOR_EXISTS_TIME + ) ).map(async (e) => { const title = (await e.getAttribute('title')) ?? ''; return { title, formattedTitle: title.toLowerCase().trim() }; @@ -139,7 +145,7 @@ export class ComboBoxService extends FtrService { const [alternate] = alternateTitle ? await this.find.allByCssSelector( - `.euiComboBoxOption[title="${alternateTitle}" i]`, + `.euiComboBoxOption[title="${alternateTitle}" i], .euiFilterSelectItem[title="${alternateTitle}" i]`, this.WAIT_FOR_EXISTS_TIME ) : []; @@ -154,12 +160,15 @@ export class ComboBoxService extends FtrService { this.log.warning( `comboBox.setElement - Could not find option [${trimmedValue}], using first` ); - const firstOption = await this.find.byCssSelector('.euiComboBoxOption', 5000); + const firstOption = await this.find.byCssSelector( + '.euiComboBoxOption, .euiFilterSelectItem', + 5000 + ); await this.clickOption(options.clickWithMouse, firstOption); } } } else { - const firstOption = await this.find.byCssSelector('.euiComboBoxOption'); + const firstOption = await this.find.byCssSelector('.euiComboBoxOption, .euiFilterSelectItem'); await this.clickOption(options.clickWithMouse, firstOption); } await this.closeOptionsList(comboBoxElement); diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index feeeab28d8fbd..9bb8be45dcdd6 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -43,6 +43,8 @@ export interface MlDataSourcePickerProps { SavedObjectFinderComponent: MlOpenSessionFlyoutProps['SavedObjectFinderComponent']; /** When true, ES|QL-based sessions are hidden from the session picker */ filterEsql?: boolean; + /** Called after a field is saved via the field editor */ + onFieldSaved?: () => void; } export const MlDataSourcePicker: FC = ({ @@ -52,6 +54,7 @@ export const MlDataSourcePicker: FC = ({ DataViewPickerComponent, SavedObjectFinderComponent, filterEsql = false, + onFieldSaved, }) => { const [savedDataViews, setSavedDataViews] = useState([]); const [isOpenSessionPanelVisible, setOpenSessionPanelVisible] = useState(false); @@ -107,11 +110,12 @@ export const MlDataSourcePicker: FC = ({ ctx: { dataView: currentDataView }, onSave: () => { dataViews.getIdsWithTitle().then(setSavedDataViews); + onFieldSaved?.(); }, }); } : undefined, - [canEditDataView, currentDataView, dataViewFieldEditor, dataViews] + [canEditDataView, currentDataView, dataViewFieldEditor, dataViews, onFieldSaved] ); const triggerLabel = useMemo( diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 7e094e2549137..8346d6844eb6c 100644 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -291,6 +291,7 @@ const DataVisualizerStateContextProvider: FC {currentDataView ? ( mlTimefilterRefresh$.next({ lastRefresh: Date.now() })} /> ) : undefined; diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer.ts index 675679812ba37..367285d490891 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer.ts @@ -32,7 +32,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { `${testData.suiteTitle} loads the index data visualizer page` ); await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( - testData.sourceIndexOrSavedSearch + testData.sourceIndexOrSavedSearch, + testData.isSavedSearch ); await headerPage.waitUntilLoadingHasFinished(); }); @@ -231,7 +232,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { `${testData.suiteTitle} loads the index data visualizer page` ); await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( - testData.sourceIndexOrSavedSearch + testData.sourceIndexOrSavedSearch, + testData.isSavedSearch ); await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_filters.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_filters.ts index 29ec6cce273f3..2ea02f2993965 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_filters.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_filters.ts @@ -60,7 +60,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `${testData.suiteTitle} loads the index data visualizer page` ); await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( - testData.sourceIndexOrSavedSearch + testData.sourceIndexOrSavedSearch, + testData.isSavedSearch ); await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); @@ -101,7 +102,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `${testData.suiteTitle} loads the index data visualizer page` ); await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( - testData.sourceIndexOrSavedSearch + testData.sourceIndexOrSavedSearch, + testData.isSavedSearch ); await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_random_sampler.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_random_sampler.ts index 1b839561e761d..a94af3e8ad790 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_random_sampler.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group1/index_data_visualizer_random_sampler.ts @@ -12,15 +12,21 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); const browser = getService('browser'); - async function goToSourceForIndexBasedDataVisualizer(sourceIndexOrSavedSearch: string) { + async function goToSourceForIndexBasedDataVisualizer( + sourceIndexOrSavedSearch: string, + isSavedSearch = false + ) { await ml.testExecution.logTestStep(`navigates to Data Visualizer page`); await ml.navigation.navigateToDataVisualizer(); - await ml.testExecution.logTestStep(`loads the saved search selection page`); + await ml.testExecution.logTestStep(`loads the data visualizer page with source picker`); await ml.dataVisualizer.navigateToDataViewSelection(); await ml.testExecution.logTestStep(`loads the index data visualizer page`); - await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(sourceIndexOrSavedSearch); + await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( + sourceIndexOrSavedSearch, + isSavedSearch + ); } describe('index based random sampler controls', function () { this.tags(['ml']); @@ -50,7 +56,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { describe('with small data sets', function () { it(`has random sampler 'on - automatic' by default`, async () => { await goToSourceForIndexBasedDataVisualizer( - farequoteDataViewTestData.sourceIndexOrSavedSearch + farequoteDataViewTestData.sourceIndexOrSavedSearch, + farequoteDataViewTestData.isSavedSearch ); await ml.dataVisualizerIndexBased.assertRandomSamplingOption( @@ -63,7 +70,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.dataVisualizerIndexBased.setRandomSamplingOption('dvRandomSamplerOptionOff'); await goToSourceForIndexBasedDataVisualizer( - farequoteLuceneSearchTestData.sourceIndexOrSavedSearch + farequoteLuceneSearchTestData.sourceIndexOrSavedSearch, + farequoteLuceneSearchTestData.isSavedSearch ); await ml.dataVisualizerIndexBased.assertRandomSamplingOption('dvRandomSamplerOptionOff'); }); diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts index 9df4c57e15278..e4cb522d40c6f 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts @@ -129,7 +129,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `${farequoteKQLFiltersSearchTestData.suiteTitle} loads the data drift view` ); await ml.jobSourceSelection.selectSourceForDataDrift( - farequoteKQLFiltersSearchTestData.sourceIndexOrSavedSearch + farequoteKQLFiltersSearchTestData.sourceIndexOrSavedSearch, + farequoteKQLFiltersSearchTestData.isSavedSearch ); await assertDataDriftPageContent(farequoteKQLFiltersSearchTestData); diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_actions_panel.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_actions_panel.ts index 13fc1de56f91c..6affae4ee0daa 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_actions_panel.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_actions_panel.ts @@ -87,7 +87,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizer.navigateToDataViewSelection(); await ml.testExecution.logTestStep('loads the index data visualizer page'); - await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(savedSearch); + await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(savedSearch, true); await ml.testExecution.logTestStep(`loads data for full time range`); await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_data_view_management.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_data_view_management.ts index a83bda167e85d..5c823883eb941 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_data_view_management.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/index_data_visualizer_data_view_management.ts @@ -28,6 +28,7 @@ interface TestData { export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); + const testSubjects = getService('testSubjects'); const originalTestData: TestData = { suiteTitle: 'original data view', @@ -201,6 +202,11 @@ export default function ({ getService }: FtrProviderContext) { ); } + await ml.testExecution.logTestStep('refreshes the data visualizer table'); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + await testSubjects.click('superDatePickerApplyTimeButton'); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + await ml.testExecution.logTestStep('displays details for added runtime metric fields'); for (const fieldRow of addDeleteFieldTestData.expected.metricFields as Array< Required @@ -246,6 +252,12 @@ export default function ({ getService }: FtrProviderContext) { newField.type ); } + + await ml.testExecution.logTestStep('refreshes the data visualizer table'); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + await testSubjects.click('superDatePickerApplyTimeButton'); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + await ml.testExecution.logTestStep('deletes newly added runtime fields'); for (const fieldToDelete of addDeleteFieldTestData.newFields!) { await ml.dataVisualizerIndexPatternManagement.deleteField(fieldToDelete.fieldName); diff --git a/x-pack/platform/test/functional/services/aiops/change_point_detection_page.ts b/x-pack/platform/test/functional/services/aiops/change_point_detection_page.ts index 15f3a69e930cc..685c0b7566c9d 100644 --- a/x-pack/platform/test/functional/services/aiops/change_point_detection_page.ts +++ b/x-pack/platform/test/functional/services/aiops/change_point_detection_page.ts @@ -32,7 +32,7 @@ export function ChangePointDetectionPageProvider( return { async navigateToDataViewSelection() { await testSubjects.click('mlMainTab changePointDetection'); - await testSubjects.existOrFail('mlPageSourceSelection'); + await testSubjects.existOrFail('mlDataSourceSelectorButton'); }, async assertChangePointDetectionPageExists() { diff --git a/x-pack/platform/test/functional/services/aiops/log_pattern_analysis_page.ts b/x-pack/platform/test/functional/services/aiops/log_pattern_analysis_page.ts index e0797875c0f94..20f4e80ca35fe 100644 --- a/x-pack/platform/test/functional/services/aiops/log_pattern_analysis_page.ts +++ b/x-pack/platform/test/functional/services/aiops/log_pattern_analysis_page.ts @@ -31,7 +31,7 @@ export function LogPatternAnalysisPageProvider({ getService, getPageObject }: Ft async navigateToDataViewSelection() { await testSubjects.click('mlMainTab logCategorization'); - await testSubjects.existOrFail('mlPageSourceSelection'); + await testSubjects.existOrFail('mlDataSourceSelectorButton'); }, async clickUseFullDataButton(expectedDocCount: number) { diff --git a/x-pack/platform/test/functional/services/aiops/log_rate_analysis_page.ts b/x-pack/platform/test/functional/services/aiops/log_rate_analysis_page.ts index 80cac6cc9071a..ceacce373b9ff 100644 --- a/x-pack/platform/test/functional/services/aiops/log_rate_analysis_page.ts +++ b/x-pack/platform/test/functional/services/aiops/log_rate_analysis_page.ts @@ -331,7 +331,7 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr async navigateToDataViewSelection() { await testSubjects.click('mlMainTab logRateAnalysis'); - await testSubjects.existOrFail('mlPageSourceSelection'); + await testSubjects.existOrFail('mlDataSourceSelectorButton'); }, async getBrushSelectionWidth(selector: string) { diff --git a/x-pack/platform/test/functional/services/ml/data_drift.ts b/x-pack/platform/test/functional/services/ml/data_drift.ts index 9f5e2ba5c3b03..de1d1bcd3de02 100644 --- a/x-pack/platform/test/functional/services/ml/data_drift.ts +++ b/x-pack/platform/test/functional/services/ml/data_drift.ts @@ -16,7 +16,7 @@ export function MachineLearningDataDriftProvider({ }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'header']); + const PageObjects = getPageObjects(['common', 'discover', 'header']); const elasticChart = getService('elasticChart'); const browser = getService('browser'); const comboBox = getService('comboBox'); @@ -33,10 +33,10 @@ export function MachineLearningDataDriftProvider({ }, async assertDataViewTitle(expectedTitle: string) { - const selector = 'mlDataDriftPageDataViewTitle'; + const selector = 'mlDataSourceSelectorButton'; await testSubjects.existOrFail(selector); await retry.tryForTime(5000, async () => { - const title = await testSubjects.getVisibleText(selector); + const title = await testSubjects.getAttribute(selector, 'title'); expect(title).to.eql( expectedTitle, `Expected data drift page's data view title to be '${expectedTitle}' (got '${title}')` @@ -242,8 +242,8 @@ export function MachineLearningDataDriftProvider({ }, async navigateToCreateNewDataViewPage() { - await retry.tryForTime(5000, async () => { - await testSubjects.click(`dataDriftCreateDataViewButton`); + await PageObjects.common.navigateToApp('ml', { path: 'data_drift_custom' }); + await retry.tryForTime(10000, async () => { await testSubjects.existOrFail(`mlPageDataDriftCustomIndexPatterns`); }); }, diff --git a/x-pack/platform/test/functional/services/ml/data_visualizer.ts b/x-pack/platform/test/functional/services/ml/data_visualizer.ts index 625de7b920979..a8c2b8aa326c6 100644 --- a/x-pack/platform/test/functional/services/ml/data_visualizer.ts +++ b/x-pack/platform/test/functional/services/ml/data_visualizer.ts @@ -64,7 +64,7 @@ export function MachineLearningDataVisualizerProvider({ getService }: FtrProvide async navigateToDataViewSelection() { await testSubjects.click('mlDataVisualizerSelectIndexButton'); - await testSubjects.existOrFail('mlPageSourceSelection'); + await testSubjects.existOrFail('mlDataSourceSelectorButton'); }, async navigateToFileUpload() { diff --git a/x-pack/platform/test/functional/services/ml/data_visualizer_index_pattern_management.ts b/x-pack/platform/test/functional/services/ml/data_visualizer_index_pattern_management.ts index 97cdbef637320..7f2c7f3328b22 100644 --- a/x-pack/platform/test/functional/services/ml/data_visualizer_index_pattern_management.ts +++ b/x-pack/platform/test/functional/services/ml/data_visualizer_index_pattern_management.ts @@ -19,14 +19,6 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( const comboBox = getService('comboBox'); return { - async assertIndexPatternManagementButtonExists() { - await testSubjects.existOrFail('dataVisualizerDataViewanagementButton'); - }, - - async assertIndexPatternManagementMenuExists() { - await testSubjects.existOrFail('dataVisualizerDataViewManagementMenu'); - }, - async assertIndexPatternFieldEditorExists() { await testSubjects.existOrFail('indexPatternFieldEditorForm', { timeout: 5000 }); }, @@ -35,31 +27,6 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( await testSubjects.missingOrFail('indexPatternFieldEditorForm', { timeout: 5000 }); }, - async clickIndexPatternManagementButton() { - await retry.tryForTime(5000, async () => { - await testSubjects.clickWhenNotDisabledWithoutRetry( - 'dataVisualizerDataViewManagementButton' - ); - await this.assertIndexPatternManagementMenuExists(); - }); - }, - - async clickAddIndexPatternFieldAction() { - await retry.tryForTime(5000, async () => { - await this.assertIndexPatternManagementMenuExists(); - await testSubjects.clickWhenNotDisabledWithoutRetry('dataVisualizerAddDataViewFieldAction'); - await this.assertIndexPatternFieldEditorExists(); - }); - }, - - async clickManageIndexPatternAction() { - await retry.tryForTime(5000, async () => { - await this.assertIndexPatternManagementMenuExists(); - await testSubjects.clickWhenNotDisabledWithoutRetry('dataVisualizerManageDataViewAction'); - await testSubjects.existOrFail('editIndexPattern'); - }); - }, - async assertIndexPatternFieldEditorFieldType(expectedIdentifier: string) { await retry.tryForTime(2000, async () => { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( @@ -74,14 +41,13 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( async setIndexPatternFieldEditorFieldType(type: string) { await comboBox.set('typeField > comboBoxInput', type); - await this.assertIndexPatternFieldEditorFieldType(type); }, async addRuntimeField(name: string, script: string, fieldType: string) { await retry.tryForTime(15 * 1000, async () => { - await this.clickIndexPatternManagementButton(); - await this.clickAddIndexPatternFieldAction(); + await testSubjects.click('mlDataSourceSelectorButton'); + await testSubjects.click('indexPattern-add-field'); await this.assertIndexPatternFieldEditorExists(); await fieldEditor.setName(name); diff --git a/x-pack/platform/test/functional/services/ml/job_source_selection.ts b/x-pack/platform/test/functional/services/ml/job_source_selection.ts index ab5928cc4aa79..08ed270a44153 100644 --- a/x-pack/platform/test/functional/services/ml/job_source_selection.ts +++ b/x-pack/platform/test/functional/services/ml/job_source_selection.ts @@ -8,6 +8,7 @@ import type { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningJobSourceSelectionProvider({ getService }: FtrProviderContext) { + const browser = getService('browser'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -23,6 +24,7 @@ export function MachineLearningJobSourceSelectionProvider({ getService }: FtrPro await this.assertSourceListContainsEntry(sourceName); }, + // Legacy method for anomaly detection and analytics jobs that still use the old page-based picker async selectSource(sourceName: string, nextPageSubj: string) { await this.filterSourceSelection(sourceName); await retry.tryForTime(30 * 1000, async () => { @@ -31,6 +33,49 @@ export function MachineLearningJobSourceSelectionProvider({ getService }: FtrPro }); }, + // Selects a data view via the inline DataViewPicker (MlDataSourcePicker) + async selectDataView(name: string, nextPageSubj: string) { + await testSubjects.click('mlDataSourceSelectorButton'); + await testSubjects.existOrFail('indexPattern-switcher', { timeout: 2000 }); + await testSubjects.setValue('indexPattern-switcher--input', name); + await retry.tryForTime(30 * 1000, async () => { + const indexPatternSwitcher = await testSubjects.find('indexPattern-switcher', 500); + await (await indexPatternSwitcher.findByCssSelector(`[title="${name}"]`)).click(); + // Wait for picker to close, confirming selection was made + await testSubjects.missingOrFail('indexPattern-switcher', { timeout: 5 * 1000 }); + }); + // Wait for URL to update with the selected data view, confirming navigation completed + await retry.tryForTime(10 * 1000, async () => { + const url = await browser.getCurrentUrl(); + if (!url.includes('index=')) { + throw new Error(`Expected URL to contain 'index=' but got: ${url}`); + } + }); + await testSubjects.existOrFail(nextPageSubj, { timeout: 30 * 1000 }); + }, + + // Selects a saved search via the "Open Discover session" flyout (MlOpenSessionFlyout) + async selectSavedSearch(name: string, nextPageSubj: string) { + await testSubjects.click('mlOpenDiscoverSessionButton'); + await testSubjects.existOrFail('loadSearchForm'); + await testSubjects.setValue('savedObjectFinderSearchInput', name, { + clearWithKeyboard: true, + }); + await retry.tryForTime(30 * 1000, async () => { + await testSubjects.clickWhenNotDisabledWithoutRetry(`savedObjectTitle${name}`); + // Wait for flyout to close, confirming selection was made + await testSubjects.missingOrFail('loadSearchForm', { timeout: 5 * 1000 }); + }); + // Wait for URL to update with savedSearchId, confirming navigation completed + await retry.tryForTime(10 * 1000, async () => { + const url = await browser.getCurrentUrl(); + if (!url.includes('savedSearchId')) { + throw new Error(`Expected URL to contain 'savedSearchId' but got: ${url}`); + } + }); + await testSubjects.existOrFail(nextPageSubj, { timeout: 30 * 1000 }); + }, + async selectSourceForAnomalyDetectionJob(sourceName: string) { await this.selectSource(sourceName, 'mlPageJobTypeSelection'); }, @@ -39,24 +84,32 @@ export function MachineLearningJobSourceSelectionProvider({ getService }: FtrPro await this.selectSource(sourceName, 'mlAnalyticsCreationContainer'); }, - async selectSourceForDataDrift(sourceName: string) { - await this.selectSource(sourceName, 'mlPageDataDrift'); + async selectSourceForDataDrift(sourceName: string, isSavedSearch = false) { + if (isSavedSearch) { + await this.selectSavedSearch(sourceName, 'mlPageDataDrift'); + } else { + await this.selectDataView(sourceName, 'mlPageDataDrift'); + } }, - async selectSourceForIndexBasedDataVisualizer(sourceName: string) { - await this.selectSource(sourceName, 'dataVisualizerIndexPage'); + async selectSourceForIndexBasedDataVisualizer(sourceName: string, isSavedSearch = false) { + if (isSavedSearch) { + await this.selectSavedSearch(sourceName, 'dataVisualizerIndexPage'); + } else { + await this.selectDataView(sourceName, 'dataVisualizerIndexPage'); + } }, async selectSourceForLogRateAnalysis(sourceName: string) { - await this.selectSource(sourceName, 'aiopsLogRateAnalysisPage'); + await this.selectDataView(sourceName, 'aiopsLogRateAnalysisPage'); }, async selectSourceForChangePointDetection(sourceName: string) { - await this.selectSource(sourceName, 'aiopsChangePointDetectionPage'); + await this.selectDataView(sourceName, 'aiopsChangePointDetectionPage'); }, async selectSourceForLogPatternAnalysisDetection(sourceName: string) { - await this.selectSource(sourceName, 'aiopsLogPatternAnalysisPage'); + await this.selectDataView(sourceName, 'aiopsLogPatternAnalysisPage'); }, }; } From 8416653ab23909b5fe341f0263448d9e91881ed5 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 22 May 2026 11:13:13 +0200 Subject: [PATCH 21/32] refactor(ml): unify two modes of Index Data Visualizer page into one - Previously Index data visualizer could be opened from ml app and anomaly detection job creation. That resulted in two different context. Now IDV card points to the ml app instead of management context - Modified MlDataSourcePicker to update the URL in place using `history.replace` for same-page query parameter changes, eliminating unnecessary navigation machinery and React warnings. - Removed `navigateToPath` enhancing code clarity and reducing dependencies. --- .../ml_data_source_picker.tsx | 31 +++++++---- .../index_data_visualizer.tsx | 9 +--- .../aiops/change_point_detection.tsx | 4 +- .../application/aiops/log_categorization.tsx | 4 +- .../application/aiops/log_rate_analysis.tsx | 4 +- .../data_drift/data_drift_page.tsx | 4 +- .../index_based/index_data_visualizer.tsx | 29 ++-------- .../jobs/new_job/pages/job_type/page.tsx | 9 +++- .../routes/anomaly_detection_management.ts | 4 -- .../routes/datavisualizer/index_based.tsx | 54 ++----------------- 10 files changed, 42 insertions(+), 110 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index 9bb8be45dcdd6..14583951cbc46 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -7,7 +7,9 @@ import type { ComponentType, FC } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; +import { parse, stringify } from 'query-string'; +import { css } from '@emotion/react'; import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; @@ -38,7 +40,6 @@ export interface MlDataSourcePickerServices extends MlOpenSessionFlyoutServices export interface MlDataSourcePickerProps { currentDataView: DataView | null; services: MlDataSourcePickerServices; - navigateToPath: (path: string) => void | Promise; DataViewPickerComponent: ComponentType; SavedObjectFinderComponent: MlOpenSessionFlyoutProps['SavedObjectFinderComponent']; /** When true, ES|QL-based sessions are hidden from the session picker */ @@ -47,10 +48,11 @@ export interface MlDataSourcePickerProps { onFieldSaved?: () => void; } +const dataViewPickerStyles = css({ minWidth: 180 }); + export const MlDataSourcePicker: FC = ({ currentDataView, services, - navigateToPath, DataViewPickerComponent, SavedObjectFinderComponent, filterEsql = false, @@ -58,6 +60,7 @@ export const MlDataSourcePicker: FC = ({ }) => { const [savedDataViews, setSavedDataViews] = useState([]); const [isOpenSessionPanelVisible, setOpenSessionPanelVisible] = useState(false); + const history = useHistory(); const location = useLocation(); const { dataViews, dataViewEditor, dataViewFieldEditor } = services; const closeFieldEditorRef = useRef<() => void | undefined>(); @@ -72,29 +75,37 @@ export const MlDataSourcePicker: FC = ({ dataViews.getIdsWithTitle().then(setSavedDataViews); }, [dataViews]); + const updateDataSource = useCallback( + (param: 'index' | 'savedSearchId', value: string) => { + const { index: _i, savedSearchId: _s, ...rest } = parse(location.search, { sort: false }); + history.replace({ search: '?' + stringify({ ...rest, [param]: value }) }); + }, + [history, location.search] + ); + const onChangeDataView = useCallback( (id: string) => { - navigateToPath(`${location.pathname}?index=${encodeURIComponent(id)}`); + updateDataSource('index', id); }, - [navigateToPath, location.pathname] + [updateDataSource] ); const onDataViewCreated = useCallback( (created: DataView) => { if (created.id) { dataViews.getIdsWithTitle().then(setSavedDataViews); - navigateToPath(`${location.pathname}?index=${encodeURIComponent(created.id)}`); + updateDataSource('index', created.id); } }, - [dataViews, navigateToPath, location.pathname] + [dataViews, updateDataSource] ); const onOpenSavedSearch = useCallback( (id: string) => { setOpenSessionPanelVisible(false); - navigateToPath(`${location.pathname}?savedSearchId=${encodeURIComponent(id)}`); + updateDataSource('savedSearchId', id); }, - [navigateToPath, location.pathname] + [updateDataSource] ); const canEditDataView = useMemo( @@ -133,7 +144,7 @@ export const MlDataSourcePicker: FC = ({ const dataViewPickerContent = ( - + ; getAdditionalLinks?: GetAdditionalLinks; headerContent?: ReactNode; - disableSearchSession?: boolean; } export type IndexDataVisualizerSpec = typeof IndexDataVisualizer; @@ -116,7 +115,6 @@ const DataVisualizerStateContextProvider: FC { const { services } = useDataVisualizerKibana(); const { @@ -134,8 +132,6 @@ const DataVisualizerStateContextProvider: FC(undefined); useEffect(() => { - if (disableSearchSession) return; - const urlState = parseUrlState(urlSearchString); if (search.session) { @@ -180,7 +176,7 @@ const DataVisualizerStateContextProvider: FC { const prevSearchString = urlSearchString; @@ -310,7 +306,6 @@ export interface Props { showFrozenDataTierChoice?: boolean; esql?: boolean; headerContent?: ReactNode; - disableSearchSession?: boolean; } export const IndexDataVisualizer: FC = ({ @@ -318,7 +313,6 @@ export const IndexDataVisualizer: FC = ({ showFrozenDataTierChoice = true, esql, headerContent, - disableSearchSession, }) => { const coreStart = getCoreStart(); const { @@ -372,7 +366,6 @@ export const IndexDataVisualizer: FC = ({ IndexDataVisualizerComponent={IndexDataVisualizerView} getAdditionalLinks={getAdditionalLinks} headerContent={headerContent} - disableSearchSession={disableSearchSession} /> ) : ( diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index 696980375c075..437a51c6534dd 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -20,7 +20,7 @@ import { MlDataSourcePicker } from '@kbn/aiops-components'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useDataSource } from '../contexts/ml/data_source_context'; -import { useMlKibana, useNavigateToPath } from '../contexts/kibana'; +import { useMlKibana } from '../contexts/kibana'; import { HelpMenu } from '../components/help_menu'; import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; @@ -29,7 +29,6 @@ import { useEnabledFeatures } from '../contexts/ml/serverless_context'; export const ChangePointDetectionPage: FC = () => { const { services } = useMlKibana(); - const navigateToPath = useNavigateToPath(); const { showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); @@ -45,7 +44,6 @@ export const ChangePointDetectionPage: FC = () => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx index 75c2189f9c9f6..1ade970d3fcbc 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_categorization.tsx @@ -18,7 +18,7 @@ import { MlDataSourcePicker } from '@kbn/aiops-components'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useDataSource } from '../contexts/ml/data_source_context'; -import { useMlKibana, useNavigateToPath } from '../contexts/kibana'; +import { useMlKibana } from '../contexts/kibana'; import { useEnabledFeatures } from '../contexts/ml'; import { HelpMenu } from '../components/help_menu'; import { MlPageHeader } from '../components/page_header'; @@ -26,7 +26,6 @@ import { PageTitle } from '../components/page_title'; export const LogCategorizationPage: FC = () => { const { services } = useMlKibana(); - const navigateToPath = useNavigateToPath(); const { showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); @@ -42,7 +41,6 @@ export const LogCategorizationPage: FC = () => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx index ea10078956f06..6985a69cf125f 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/log_rate_analysis.tsx @@ -18,7 +18,7 @@ import { MlDataSourcePicker } from '@kbn/aiops-components'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useDataSource } from '../contexts/ml/data_source_context'; -import { useMlKibana, useNavigateToPath } from '../contexts/kibana'; +import { useMlKibana } from '../contexts/kibana'; import { HelpMenu } from '../components/help_menu'; import { useEnabledFeatures } from '../contexts/ml'; import { MlPageHeader } from '../components/page_header'; @@ -26,7 +26,6 @@ import { PageTitle } from '../components/page_title'; export const LogRateAnalysisPage: FC = () => { const { services } = useMlKibana(); - const navigateToPath = useNavigateToPath(); const { showContextualInsights, showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); @@ -39,7 +38,6 @@ export const LogRateAnalysisPage: FC = () => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx index a3f54105fa93b..b81cc112043a0 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx @@ -14,7 +14,7 @@ import type { DataDriftSpec } from '@kbn/data-visualizer-plugin/public'; import { MlDataSourcePicker } from '@kbn/aiops-components'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; -import { useMlKibana, useNavigateToPath } from '../../contexts/kibana'; +import { useMlKibana } from '../../contexts/kibana'; import { useDataSource } from '../../contexts/ml'; import { MlPageHeader } from '../../components/page_header'; import { PageTitle } from '../../components/page_title'; @@ -22,7 +22,6 @@ import { PageTitle } from '../../components/page_title'; export const DataDriftPage: FC = () => { const { services } = useMlKibana(); const { dataVisualizer } = services; - const navigateToPath = useNavigateToPath(); const [DataDriftView, setDataDriftView] = useState(null); @@ -39,7 +38,6 @@ export const DataDriftPage: FC = () => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index f35c181e0f8a9..8e4d2e40ff137 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -6,12 +6,12 @@ */ import type { FC } from 'react'; -import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { IndexDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public'; import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; -import { EuiEmptyPrompt, EuiHorizontalRule } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import type { GetAdditionalLinksParams, @@ -22,7 +22,7 @@ import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; import { MlDataSourcePicker } from '@kbn/aiops-components'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; -import { useMlApi, useMlKibana, useMlLocator, useNavigateToPath } from '../../contexts/kibana'; +import { useMlApi, useMlKibana, useMlLocator } from '../../contexts/kibana'; import { HelpMenu } from '../../components/help_menu'; import { isFullLicense } from '../../license'; import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes'; @@ -33,10 +33,7 @@ import { useDataSource } from '../../contexts/ml/data_source_context'; import { useMlManagementLocator } from '../../contexts/kibana/use_create_url'; import { PageTitle } from '../../components/page_title'; -export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: boolean }> = ({ - esql = false, - isManagementContext, -}) => { +export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); const { services } = useMlKibana(); const { @@ -49,7 +46,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: b mlApi: { recognizeIndex }, }, } = services; - const navigateToPath = useNavigateToPath(); const mlApi = useMlApi(); const { showNodeInfo } = useEnabledFeatures(); const { selectedDataView: dataView } = useDataSource(); @@ -57,20 +53,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: b const mlManagementLocator = useMlManagementLocator(); const mlFeaturesDisabled = !isFullLicense(); - const dataSourceNavigate = useCallback( - async (path: string) => { - if (isManagementContext && mlManagementLocator) { - await mlManagementLocator.navigate({ - sectionId: 'ml', - appId: `anomaly_detection${path}`, - }); - } else { - await navigateToPath(path); - } - }, - [isManagementContext, navigateToPath, mlManagementLocator] - ); - useEffect(() => { getMlNodeCount(mlApi); }, [mlApi]); @@ -220,7 +202,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: b - {isManagementContext && } {!dataView && !esql ? ( <> {dataSourcePicker} @@ -278,7 +258,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean; isManagementContext: b showFrozenDataTierChoice={showNodeInfo} esql={esql} headerContent={dataSourcePicker} - disableSearchSession={isManagementContext} /> )} diff --git a/x-pack/platform/plugins/shared/ml/public/application/jobs/new_job/pages/job_type/page.tsx b/x-pack/platform/plugins/shared/ml/public/application/jobs/new_job/pages/job_type/page.tsx index 86c14b14903b4..16ea3a46269ad 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/jobs/new_job/pages/job_type/page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/jobs/new_job/pages/job_type/page.tsx @@ -22,7 +22,11 @@ import { ES_FIELD_TYPES } from '@kbn/field-types'; import { ML_APP_LOCATOR } from '@kbn/ml-common-types/locator_app_locator'; import { ML_PAGES } from '@kbn/ml-common-types/locator_ml_pages'; import { PageTitle } from '../../../../components/page_title'; -import { useMlKibana, useMlManagementLocator } from '../../../../contexts/kibana'; +import { + useMlKibana, + useMlManagementLocator, + useNavigateToPath, +} from '../../../../contexts/kibana'; import { useDataSource } from '../../../../contexts/ml'; import { DataRecognizer } from '../../../../components/data_recognizer'; @@ -52,6 +56,7 @@ export const Page: FC = () => { const isTimeBasedIndex: boolean = selectedDataView.isTimeBased(); + const navigateToPath = useNavigateToPath(); const mlManagementLocator = useMlManagementLocator(); const navigateToManagementPath = async (path: string) => { @@ -150,7 +155,7 @@ export const Page: FC = () => { dataVisualizerLink, recentlyAccessed ); - navigateToManagementPath(`/jobs/new_job/datavisualizer${getUrlParams()}`); + navigateToPath(`/${ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER}${getUrlParams()}`); }; const jobTypes = [ diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts index 214b74d0fb535..f6737ffbc3863 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts @@ -10,9 +10,5 @@ export * from './datavisualizer/data_drift'; export * from './datavisualizer/data_comparison'; export * from './datavisualizer/datavisualizer'; export * from './datavisualizer/file_based'; -export { - indexBasedManagementRouteFactory, - indexESQLBasedManagementRouteFactory, -} from './datavisualizer/index_based'; export * from './jobs_list'; export * from './supplied_configurations'; diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx index 93aadb05b5c87..147f5ed309aa8 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/datavisualizer/index_based.tsx @@ -14,12 +14,7 @@ import type { NavigateToPath } from '../../../contexts/kibana'; import type { MlRoute } from '../../router'; import { createPath, PageLoader } from '../../router'; import { useRouteResolver } from '../../use_resolver'; -import type { NavigateToApp } from '../../breadcrumbs'; -import { - getBreadcrumbWithUrlForApp, - getStackManagementBreadcrumb, - getMlManagementBreadcrumb, -} from '../../breadcrumbs'; +import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; import { DataSourceContextProvider } from '../../../contexts/ml'; const Page = dynamic(async () => ({ @@ -36,7 +31,7 @@ export const indexBasedRouteFactory = ( title: i18n.translate('xpack.ml.dataVisualizer.dataView.docTitle', { defaultMessage: 'Index data visualizer', }), - render: () => , + render: () => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), @@ -57,7 +52,7 @@ export const indexESQLBasedRouteFactory = ( title: i18n.translate('xpack.ml.dataVisualizer.esql.docTitle', { defaultMessage: 'Index data visualizer (ES|QL)', }), - render: () => , + render: () => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), @@ -69,52 +64,13 @@ export const indexESQLBasedRouteFactory = ( ], }); -export const indexBasedManagementRouteFactory = (navigateToApp: NavigateToApp): MlRoute => ({ - id: 'indexDataVisualizer', - path: createPath(ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER), - title: i18n.translate('xpack.ml.dataVisualizer.dataView.docTitle', { - defaultMessage: 'Index data visualizer', - }), - render: () => , - breadcrumbs: [ - getStackManagementBreadcrumb(navigateToApp), - getMlManagementBreadcrumb('ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB', navigateToApp), - { - text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataViewLabel', { - defaultMessage: 'Index data visualizer', - }), - }, - ], -}); - -export const indexESQLBasedManagementRouteFactory = (navigateToApp: NavigateToApp): MlRoute => ({ - id: 'esqlDataVisualizer', - path: createPath(ML_PAGES.DATA_VISUALIZER_ESQL), - title: i18n.translate('xpack.ml.dataVisualizer.esql.docTitle', { - defaultMessage: 'Index data visualizer (ES|QL)', - }), - render: () => , - breadcrumbs: [ - getStackManagementBreadcrumb(navigateToApp), - getMlManagementBreadcrumb('ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB', navigateToApp), - { - text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.esqlLabel', { - defaultMessage: 'Index data visualizer (ES|QL)', - }), - }, - ], -}); - -const PageWrapper: FC<{ esql: boolean; isManagementContext: boolean }> = ({ - esql, - isManagementContext, -}) => { +const PageWrapper: FC<{ esql: boolean }> = ({ esql }) => { const { context } = useRouteResolver('basic', []); return ( - + ); From f2f0d7a898b672117241c3394519fc8290f0f011 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Fri, 22 May 2026 13:09:25 +0200 Subject: [PATCH 22/32] test(ml): enhance DataSourceContextProvider tests for default data view handling - Updated tests for DataSourceContextProvider to improve coverage on default data view scenarios, including cases with managed data views and error handling when no default data view is available. - Refactored test descriptions for clarity, ensuring they accurately reflect the behavior being tested. - Ensured that children components render correctly under various conditions, enhancing reliability of the test suite. --- .../ml_data_source_picker.test.tsx | 16 +++---- .../contexts/ml/data_source_context.test.tsx | 46 ++++++++++++++++++- .../contexts/ml/data_source_context.tsx | 8 +--- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx index b976ac13e55ba..aaff44caa849b 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.test.tsx @@ -11,7 +11,9 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { MlDataSourcePicker } from './ml_data_source_picker'; import type { MlDataSourcePickerServices } from './ml_data_source_picker'; +const mockHistoryReplace = jest.fn(); jest.mock('react-router-dom', () => ({ + useHistory: jest.fn(() => ({ replace: mockHistoryReplace })), useLocation: jest.fn(() => ({ pathname: '/jobs/new_job/step/data_view', search: '' })), })); @@ -43,7 +45,6 @@ jest.mock('./ml_open_session_flyout', () => ({ }, })); -const mockNavigateToPath = jest.fn(); const mockGetIdsWithTitle = jest.fn().mockResolvedValue([]); const mockOpenEditor = jest.fn().mockResolvedValue(() => {}); @@ -71,7 +72,6 @@ const renderComponent = (props: { currentDataView: any; services?: MlDataSourceP @@ -118,9 +118,7 @@ describe('MlDataSourcePicker', () => { capturedDataViewPickerProps.onChangeDataView('test-index-id'); }); - expect(mockNavigateToPath).toHaveBeenCalledWith( - '/jobs/new_job/step/data_view?index=test-index-id' - ); + expect(mockHistoryReplace).toHaveBeenCalledWith({ search: '?index=test-index-id' }); }); it('renders MlOpenSessionFlyout when "Open Discover session" button is clicked', async () => { @@ -152,9 +150,9 @@ describe('MlDataSourcePicker', () => { fireEvent.click(screen.getByTestId('openSavedSearch')); }); - expect(mockNavigateToPath).toHaveBeenCalledWith( - '/jobs/new_job/step/data_view?savedSearchId=saved-search-id-1' - ); + expect(mockHistoryReplace).toHaveBeenCalledWith({ + search: '?savedSearchId=saved-search-id-1', + }); expect(screen.queryByTestId('mockOpenSessionFlyout')).toBeNull(); }); @@ -170,7 +168,7 @@ describe('MlDataSourcePicker', () => { await capturedDataViewPickerProps.onDataViewCreated({ id: 'new-dv-id' }); }); - expect(mockNavigateToPath).toHaveBeenCalledWith('/jobs/new_job/step/data_view?index=new-dv-id'); + expect(mockHistoryReplace).toHaveBeenCalledWith({ search: '?index=new-dv-id' }); expect(mockGetIdsWithTitle).toHaveBeenCalledTimes(2); }); diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx index 395ca393ab3df..ddd0aa71c0388 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx @@ -61,7 +61,7 @@ describe('DataSourceContextProvider', () => { (useMlKibana as jest.Mock).mockReturnValue(buildKibanaMock()); }); - it('renders children when default data view is found (no URL params)', async () => { + it('uses default data view when no URL params are present', async () => { mockLocationSearch.mockReturnValue(''); mockGetDefaultDataView.mockResolvedValue({ id: 'default-dv', @@ -77,6 +77,50 @@ describe('DataSourceContextProvider', () => { expect(mockGetDefaultDataView).toHaveBeenCalled(); }); + it('uses managed default data view directly without falling back to list', async () => { + mockLocationSearch.mockReturnValue(''); + mockGetDefaultDataView.mockResolvedValue({ + id: 'logs-star', + title: 'logs-*', + managed: true, + }); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGetDefaultDataView).toHaveBeenCalled(); + }); + + it('renders children with null data view when getDefaultDataView throws', async () => { + mockLocationSearch.mockReturnValue(''); + mockGetDefaultDataView.mockRejectedValue(new Error('No default data view')); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGetDefaultDataView).toHaveBeenCalled(); + expect(mockGet).not.toHaveBeenCalled(); + }); + + it('renders children with null data view when no default and no data views exist', async () => { + mockLocationSearch.mockReturnValue(''); + mockGetDefaultDataView.mockResolvedValue(null); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGet).not.toHaveBeenCalled(); + }); + it('renders children when index URL param is present', async () => { mockLocationSearch.mockReturnValue('?index=my-index-id'); mockGet.mockResolvedValue({ diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx index d7ee0638b4783..6cb814b1de74e 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx @@ -73,18 +73,14 @@ export const DataSourceContextProvider: FC> = ({ chil ); } - let dataViewAndSavedSearch: DataViewAndSavedSearch = { - savedSearch: null, - dataView: null, - }; + let dataViewAndSavedSearch: DataViewAndSavedSearch = { savedSearch: null, dataView: null }; if (savedSearchId !== undefined) { dataViewAndSavedSearch = await getDataViewAndSavedSearchCb(savedSearchId); } else if (dataViewId !== undefined) { dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId); } else { - const defaultDataView = await dataViews.getDefaultDataView().catch(() => null); - dataViewAndSavedSearch.dataView = defaultDataView ?? null; + dataViewAndSavedSearch.dataView = await dataViews.getDefaultDataView().catch(() => null); } const { savedSearch, dataView } = dataViewAndSavedSearch; From 5930d3025ad5d50ffbd021230bde502df3aa7acd Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Sun, 24 May 2026 14:21:48 +0200 Subject: [PATCH 23/32] refactor(ml): remove DATA_DRIFT_INDEX_SELECT references and update routing - Removed the DATA_DRIFT_INDEX_SELECT constant and its references from locator files, streamlining the ML_PAGES structure. - Updated the DataVisualizerGrid component to redirect to the DATA_DRIFT page instead of the removed index select path. - Cleaned up breadcrumbs by eliminating the DATA_DRIFT_INDEX_SELECT_BREADCRUMB, enhancing navigation clarity. - Refactored routing logic in the data drift component to remove unnecessary redirection code, improving maintainability. - Added new test cases for creating data views via the MlDataSourcePicker, ensuring comprehensive coverage of the data drift functionality. --- .../journeys_e2e/tsdb_logs_data_visualizer.ts | 18 +++++-- .../shared/ml/common-types/locator.ts | 1 - .../ml/common-types/locator_ml_pages.ts | 1 - .../overview/data_visualizer_grid.tsx | 2 +- .../public/application/routing/breadcrumbs.ts | 9 ---- .../routes/datavisualizer/data_drift.tsx | 11 ----- .../ml/data_visualizer/group2/data_drift.ts | 24 +++++++++ .../test/functional/services/ml/data_drift.ts | 49 +++++++++++++++++++ .../index_data_visualizer_actions_panel.ts | 2 +- 9 files changed, 88 insertions(+), 29 deletions(-) diff --git a/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts index 5bc7aac048438..00415963f8ac8 100644 --- a/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts +++ b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts @@ -8,6 +8,8 @@ import { Journey } from '@kbn/journeys'; import { subj } from '@kbn/test-subj-selector'; +const DATA_VIEW_NAME = 'Kibana Sample Data Logs (TSDB)'; + export const journey = new Journey({ kbnArchives: ['src/platform/test/functional/fixtures/kbn_archiver/kibana_sample_data_logs_tsdb'], esArchives: ['src/platform/test/functional/fixtures/es_archiver/kibana_sample_data_logs_tsdb'], @@ -18,13 +20,19 @@ export const journey = new Journey({ await page.waitForSelector(subj('mlDataVisualizerCardIndexData')); await page.waitForSelector(subj('globalLoadingIndicator-hidden')); }) - .step('Go to data view selection', async ({ page }) => { - const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); + .step('Go to Index data visualizer', async ({ page }) => { + const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); // this stays await createButtons.first().click(); - await page.waitForSelector(subj('savedObjectsFinderTable')); + await page.waitForSelector(subj('dataVisualizerIndexPage')); }) - .step('Go to Index data visualizer', async ({ page, kibanaPage }) => { - await page.click(subj('savedObjectTitlekibana_sample_data_logstsdb')); + .step('Go to Data View selection', async ({ page, kibanaPage }) => { + await page.click(subj('mlDataSourceSelectorButton')); + await page.waitForSelector(subj('indexPattern-switcher')); + await page.locator(subj('indexPattern-switcher--input')).fill(DATA_VIEW_NAME); + await page + .locator(subj('indexPattern-switcher')) + .locator(`[title="${DATA_VIEW_NAME}"]`) + .click(); await page.click(subj('mlDatePickerButtonUseFullData')); await kibanaPage.waitForHeader(); await page.waitForSelector(subj('dataVisualizerTable-loaded'), { timeout: 60000 }); diff --git a/x-pack/platform/packages/shared/ml/common-types/locator.ts b/x-pack/platform/packages/shared/ml/common-types/locator.ts index cc329b354600c..19ba7820a76f3 100644 --- a/x-pack/platform/packages/shared/ml/common-types/locator.ts +++ b/x-pack/platform/packages/shared/ml/common-types/locator.ts @@ -66,7 +66,6 @@ export type MlGenericUrlState = MLPageState< | typeof ML_PAGES.CALENDARS_MANAGE | typeof ML_PAGES.CALENDARS_NEW | typeof ML_PAGES.DATA_DRIFT_CUSTOM - | typeof ML_PAGES.DATA_DRIFT_INDEX_SELECT | typeof ML_PAGES.DATA_DRIFT | typeof ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB | typeof ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION diff --git a/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts b/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts index a0a870276c4d8..f93338a61ec62 100644 --- a/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts +++ b/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts @@ -15,7 +15,6 @@ export const ML_PAGES = { DATA_FRAME_ANALYTICS_SOURCE_SELECTION: 'data_frame_analytics/source_selection', DATA_FRAME_ANALYTICS_CREATE_JOB: 'data_frame_analytics/new_job', TRAINED_MODELS_MANAGE: 'trained_models', - DATA_DRIFT_INDEX_SELECT: 'data_drift_index_select', DATA_DRIFT_CUSTOM: 'data_drift_custom', DATA_DRIFT: 'data_drift', NODES: 'nodes', diff --git a/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx index b4b23a4be3303..9f9f374aa9b70 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx @@ -47,7 +47,7 @@ export const DataVisualizerGrid: FC<{ isEsqlEnabled: boolean; cardTitleSize?: 's ({ - id: 'dataDrift', - path: createPath(ML_PAGES.DATA_DRIFT_INDEX_SELECT), - render: ({ location }) => ( - - ), - breadcrumbs: [], - 'data-test-subj': 'mlPageDataDrift', -}); - export const dataDriftRouteIndexPatternFactory = ( navigateToPath: NavigateToPath, basePath: string diff --git a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts index e4cb522d40c6f..9b8786555b7b1 100644 --- a/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts +++ b/x-pack/platform/test/functional/apps/ml/data_visualizer/group2/data_drift.ts @@ -105,6 +105,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ml.testResources.deleteDataViewByTitle('ft_fare*,ft_fareq*'), ml.testResources.deleteDataViewByTitle('ft_farequote'), ml.testResources.deleteDataViewByTitle('ft_ihp_outlier'), + ml.testResources.deleteDataViewByTitle('ft_fare*_picker_test'), ]); }); @@ -238,5 +239,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await ml.dataDrift.runAnalysis(); }); }); + + describe('creates a new data view via the MlDataSourcePicker', function () { + it('opens the data view editor from picker and loads drift after creation', async () => { + await ml.navigation.navigateToMl(); + await elasticChart.setNewChartUiDebugFlag(true); + await ml.navigation.navigateToDataDrift(); + + await ml.testExecution.logTestStep('opens the create data view flyout from the picker'); + await ml.dataDrift.openCreateDataViewFromPicker(); + + await ml.testExecution.logTestStep('creates a data view via the flyout'); + await ml.dataDrift.createDataViewViaFlyout({ + name: 'ft_fare*_picker_test', + indexPattern: 'ft_fare*', + timeField: '@timestamp', + }); + + await ml.testExecution.logTestStep('verifies data drift page loads with new data view'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await ml.dataDrift.assertDataViewTitle('ft_fare*_picker_test'); + await ml.dataDrift.assertTimeRangeSelectorSectionExists(); + }); + }); }); } diff --git a/x-pack/platform/test/functional/services/ml/data_drift.ts b/x-pack/platform/test/functional/services/ml/data_drift.ts index de1d1bcd3de02..33f5601e0ccb8 100644 --- a/x-pack/platform/test/functional/services/ml/data_drift.ts +++ b/x-pack/platform/test/functional/services/ml/data_drift.ts @@ -358,5 +358,54 @@ export function MachineLearningDataDriftProvider({ await this.assertDataDriftTimestampField(timeFieldName); }, + + async openDataViewPicker() { + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.click('mlDataSourceSelectorButton'); + await testSubjects.existOrFail('changeDataViewPopover'); + }); + }, + + async openCreateDataViewFromPicker() { + await retry.tryForTime(10 * 1000, async () => { + await this.openDataViewPicker(); + await testSubjects.click('dataview-create-new'); + }); + }, + + async createDataViewViaFlyout({ + name, + indexPattern, + timeField, + }: { + name: string; + indexPattern: string; + timeField?: string; + }) { + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.existOrFail('createIndexPatternNameInput'); + }); + + await testSubjects.setValue('createIndexPatternNameInput', name); + await testSubjects.setValue('createIndexPatternTitleInput', indexPattern); + + if (timeField) { + await testSubjects.click('toggleAdvancedSetting'); + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.existOrFail('allowHiddenField'); + }); + const timeFieldInput = 'timestampField'; + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.existOrFail(timeFieldInput); + }); + await testSubjects.click(timeFieldInput); + await testSubjects.setValue(timeFieldInput, timeField); + } + + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.click('saveIndexPatternButton'); + await testSubjects.missingOrFail('createIndexPatternNameInput'); + }); + }, }; } diff --git a/x-pack/platform/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts b/x-pack/platform/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts index 409ef1ff2b6b4..99021c6dbe404 100644 --- a/x-pack/platform/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts +++ b/x-pack/platform/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts @@ -37,7 +37,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizer.navigateToDataViewSelection(); await ml.testExecution.logTestStep('loads the index data visualizer page'); - await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(savedSearch); + await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(savedSearch, true); }); it('navigates to Discover page', async () => { From 60b815897ee71dd00fe530cb2b5dfa82d5f83d46 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 25 May 2026 09:59:36 +0200 Subject: [PATCH 24/32] refactor(ml): clean up unused constants and breadcrumbs - Removed obsolete constants related to log rate analysis and log pattern analysis from breadcrumbs, streamlining the routing structure. - Eliminated the DATA_VISUALIZER_INDEX_SELECT constant from ML_PAGES, enhancing clarity in page navigation. - Updated the breadcrumbs configuration to reflect the changes, improving overall navigation consistency. --- .../ml/common-types/locator_ml_pages.ts | 5 -- .../translations/translations/de-DE.json | 3 - .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../public/application/routing/breadcrumbs.ts | 61 ------------------- 6 files changed, 78 deletions(-) diff --git a/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts b/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts index f93338a61ec62..c8eed5b2a26e7 100644 --- a/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts +++ b/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts @@ -26,11 +26,6 @@ export const ML_PAGES = { * Page: Data Visualizer */ DATA_VISUALIZER: 'datavisualizer', - /** - * Page: Data Visualizer - * Open data visualizer by selecting a Kibana data view or saved search - */ - DATA_VISUALIZER_INDEX_SELECT: 'datavisualizer_index_select', /** * Page: Data Visualizer * Open data visualizer by importing data from a log file diff --git a/x-pack/platform/plugins/private/translations/translations/de-DE.json b/x-pack/platform/plugins/private/translations/translations/de-DE.json index ad58fdccc7a4a..c08f212392b7c 100644 --- a/x-pack/platform/plugins/private/translations/translations/de-DE.json +++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json @@ -29264,11 +29264,8 @@ "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeDesc": "Verwenden Sie den Standard-Zeitfilter im Single Metric Viewer und Anomaly Explorer. Wenn nicht aktiviert, werden die Ergebnisse für den gesamten Zeitraum des Jobs angezeigt.", "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeName": "Aktivieren Sie die Standardwerte für Zeitfilter für die Ergebnisse der Anomalieerkennung", "xpack.ml.aiops.changePointDetection.docTitle": "Änderungspunkterkennung", - "xpack.ml.aiops.changePointDetectionBreadcrumbLabel": "Änderungspunkterkennung", "xpack.ml.aiops.logCategorization.docTitle": "Logmusteranalyse", - "xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel": "Logmusteranalyse", "xpack.ml.aiops.logRateAnalysis.docTitle": "Logratenanalyse", - "xpack.ml.aiops.logRateAnalysisBreadcrumbLabel": "Analyse der Log-Rate", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "Änderungspunkterkennung", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "Analyse der Log-Rate", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "Das Prüfintervall ist größer als das Lookback-Intervall. Reduzieren Sie es auf {lookbackInterval}, um möglicherweise fehlende Benachrichtigungen zu vermeiden.", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index fb9f51e4c21e6..59999223f7067 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -29217,11 +29217,8 @@ "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeDesc": "Utilisez le filtre temporel par défaut dans Single Metric Viewer et Anomaly Explorer. Si l'option n'est pas activée, les résultats sont affichés pour la plage temporelle entière de la tâche.", "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeName": "Activer les valeurs par défaut du filtre temporel pour les résultats de détection des anomalies", "xpack.ml.aiops.changePointDetection.docTitle": "Modifier la détection du point", - "xpack.ml.aiops.changePointDetectionBreadcrumbLabel": "Modifier la détection du point", "xpack.ml.aiops.logCategorization.docTitle": "Analyse du modèle de log", - "xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel": "Analyse du modèle de log", "xpack.ml.aiops.logRateAnalysis.docTitle": "Analyse du taux de log", - "xpack.ml.aiops.logRateAnalysisBreadcrumbLabel": "Analyse du taux de log", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "Modifier la détection du point", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "Analyse du taux de log", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "L'intervalle de vérification est supérieur à l'intervalle d'historique. Réduisez-le à {lookbackInterval} pour éviter des notifications potentiellement manquantes.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index b4b0f8f77be31..8e11fbcbffd07 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -29364,11 +29364,8 @@ "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeDesc": "シングルメトリックビューアーと異常エクスプローラーでデフォルト時間フィルターを使用します。有効ではない場合、ジョブの全時間範囲の結果が表示されます。", "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeName": "異常検知結果の時間フィルターデフォルトを有効にする", "xpack.ml.aiops.changePointDetection.docTitle": "変化点検出", - "xpack.ml.aiops.changePointDetectionBreadcrumbLabel": "変化点検出", "xpack.ml.aiops.logCategorization.docTitle": "ログパターン分析", - "xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel": "ログパターン分析", "xpack.ml.aiops.logRateAnalysis.docTitle": "ログレート分析", - "xpack.ml.aiops.logRateAnalysisBreadcrumbLabel": "ログレート分析", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "変化点検出", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "ログレート分析", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "チェック間隔がルックバック間隔を超えています。通知を見逃す可能性を回避するには、{lookbackInterval}に減らします。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 8b7bf41eed79d..971080ca8daf2 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -29368,11 +29368,8 @@ "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeDesc": "使用 Single Metric Viewer 和 Anomaly Explorer 中的默认时间筛选。如果未启用,则将显示作业的整个时间范围的结果。", "xpack.ml.advancedSettings.enableAnomalyDetectionDefaultTimeRangeName": "对异常检测结果启用时间筛选默认值", "xpack.ml.aiops.changePointDetection.docTitle": "更改点检测", - "xpack.ml.aiops.changePointDetectionBreadcrumbLabel": "变更点检测", "xpack.ml.aiops.logCategorization.docTitle": "日志模式分析", - "xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel": "日志模式分析", "xpack.ml.aiops.logRateAnalysis.docTitle": "日志速率分析", - "xpack.ml.aiops.logRateAnalysisBreadcrumbLabel": "日志速率分析", "xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel": "更改点检测", "xpack.ml.aiopsBreadcrumbs.logRateAnalysisLabel": "日志速率分析", "xpack.ml.alertConditionValidation.alertIntervalTooHighMessage": "检查时间间隔大于回溯时间间隔。将其减少为 {lookbackInterval} 以避免通知可能丢失。", diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts index 4a4010d5cfcf5..b211d68e85b79 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/breadcrumbs.ts @@ -120,52 +120,6 @@ export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ deepLinkId: 'ml:dataVisualizer', }); -export const LOG_RATE_ANALYSIS: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiops.logRateAnalysisBreadcrumbLabel', { - defaultMessage: 'Log rate analysis', - }), - href: '/aiops/log_rate_analysis', - deepLinkId: 'ml:logRateAnalysis', -}); - -export const LOG_RATE_ANALYSIS_PAGE: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiops.logRateAnalysisBreadcrumbLabel', { - defaultMessage: 'Log rate analysis', - }), - href: '/aiops/log_rate_analysis', - deepLinkId: 'ml:logRateAnalysisPage', -}); -export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel', { - defaultMessage: 'Log pattern analysis', - }), - href: '/aiops/log_categorization', - deepLinkId: 'ml:logPatternAnalysis', -}); - -export const LOG_PATTERN_ANALYSIS_PAGE: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel', { - defaultMessage: 'Log pattern analysis', - }), - href: '/aiops/log_categorization', - deepLinkId: 'ml:logPatternAnalysisPage', -}); -export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiops.changePointDetectionBreadcrumbLabel', { - defaultMessage: 'Change point detection', - }), - href: '/aiops/change_point_detection', - deepLinkId: 'ml:changePointDetections', -}); - -export const CHANGE_POINT_DETECTION_PAGE: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiops.changePointDetectionBreadcrumbLabel', { - defaultMessage: 'Change point detection', - }), - href: '/aiops/change_point_detection', - deepLinkId: 'ml:changePointDetectionsPage', -}); - export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.settings.breadcrumbs.dataComparisonLabel', { defaultMessage: 'Data drift', @@ -174,14 +128,6 @@ export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ deepLinkId: 'ml:dataDrift', }); -export const DATA_DRIFT_PAGE: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.settings.breadcrumbs.dataComparisonLabel', { - defaultMessage: 'Data drift', - }), - href: '/data_drift', - deepLinkId: 'ml:dataDriftPage', -}); - const managementBreadcrumbs = { ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB, CALENDAR_DST_LISTS_MANAGEMENT_BREADCRUMB, @@ -197,14 +143,7 @@ type ManagementBreadcrumb = keyof typeof managementBreadcrumbs; const breadcrumbs = { ML_BREADCRUMB, - DATA_DRIFT_PAGE, DATA_VISUALIZER_BREADCRUMB, - LOG_RATE_ANALYSIS, - LOG_RATE_ANALYSIS_PAGE, - LOG_PATTERN_ANALYSIS, - LOG_PATTERN_ANALYSIS_PAGE, - CHANGE_POINT_DETECTION, - CHANGE_POINT_DETECTION_PAGE, }; type Breadcrumb = keyof typeof breadcrumbs; From 1845e0354d5dbd993b242ff5bd069d47fc6624fd Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 25 May 2026 11:49:35 +0200 Subject: [PATCH 25/32] refactor(ml): remove pageTitle prop from various components and introduce NoDataViewPrompt - Eliminated the `pageTitle` prop from several components including LogCategorization, LogRateAnalysis, and ChangePointDetection, simplifying their interfaces. - Introduced a new `NoDataViewPrompt` component to handle scenarios where no data view is selected, enhancing user experience with a consistent prompt across different pages. - Updated related components to utilize the new prompt, ensuring clarity when no data view is available. --- .../journeys_e2e/tsdb_logs_data_visualizer.ts | 2 +- .../ml_open_session_flyout.tsx | 5 +-- .../change_point_detection_root.tsx | 9 +----- .../log_categorization_app_state.tsx | 5 +-- .../log_categorization_page.tsx | 7 ++-- .../log_rate_analysis_app_state.tsx | 4 --- .../log_rate_analysis_page.tsx | 4 +-- .../components/page_header/page_header.tsx | 21 ++++-------- .../aiops/change_point_detection.tsx | 23 ++----------- .../application/aiops/log_categorization.tsx | 22 ++----------- .../application/aiops/log_rate_analysis.tsx | 22 ++----------- .../application/aiops/no_data_view_prompt.tsx | 32 +++++++++++++++++++ 12 files changed, 52 insertions(+), 104 deletions(-) create mode 100644 x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx diff --git a/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts index 00415963f8ac8..ba63259dc4b86 100644 --- a/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts +++ b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts @@ -21,7 +21,7 @@ export const journey = new Journey({ await page.waitForSelector(subj('globalLoadingIndicator-hidden')); }) .step('Go to Index data visualizer', async ({ page }) => { - const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); // this stays + const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); await createButtons.first().click(); await page.waitForSelector(subj('dataVisualizerIndexPage')); }) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx index 59c95ab37a8e1..df7f2f53dda52 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -116,10 +116,7 @@ export const MlOpenSessionFlyout: FC = ({ : {}), }, ]} - onChoose={(id) => { - onOpenSavedSearch(id); - onClose(); - }} + onChoose={onOpenSavedSearch} showFilter /> diff --git a/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx b/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx index dd443323c4499..3f3d36edf5606 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx @@ -55,8 +55,6 @@ export interface ChangePointDetectionAppStateProps { appContextValue: AiopsAppContextValue; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; - /** Optional page title for screen readers */ - pageTitle?: ReactNode; /** * Optional data source picker rendered in the page header. When provided it * replaces the static data view title. Typically a `DataDriftDataSourcePicker`- @@ -72,7 +70,6 @@ export const ChangePointDetectionAppState: FC savedSearch, appContextValue, showFrozenDataTierChoice = true, - pageTitle, headerContent, rightSideItems, }) => { @@ -136,11 +133,7 @@ export const ChangePointDetectionAppState: FC - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx index 38198a2dc87b1..9ee22e523ca2b 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx @@ -39,8 +39,6 @@ export interface LogCategorizationAppStateProps { appContextValue: AiopsAppContextValue; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; - /** Optional page title for screen readers */ - pageTitle?: ReactNode; /** * Optional data source picker rendered in the page header. When provided it * replaces the static data view title. Typically a `DataDriftDataSourcePicker`- @@ -54,7 +52,6 @@ export const LogCategorizationAppState: FC = ({ savedSearch, appContextValue, showFrozenDataTierChoice = true, - pageTitle, headerContent, }) => { if (!dataView) { @@ -107,7 +104,7 @@ export const LogCategorizationAppState: FC = ({ - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx index 44e56cf42e157..35708f0f2a67a 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -60,10 +60,7 @@ import { AttachmentsMenu } from './attachments_menu'; const BAR_TARGET = 20; const DEFAULT_SELECTED_FIELD = 'message'; -export const LogCategorizationPage: FC<{ pageTitle?: ReactNode; headerContent?: ReactNode }> = ({ - pageTitle, - headerContent, -}) => { +export const LogCategorizationPage: FC<{ headerContent?: ReactNode }> = ({ headerContent }) => { const { notifications: { toasts }, embeddingOrigin, @@ -352,7 +349,7 @@ export const LogCategorizationPage: FC<{ pageTitle?: ReactNode; headerContent?: return ( - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index 0308a444d973c..f6147f42457ac 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -44,8 +44,6 @@ export interface LogRateAnalysisAppStateProps { showContextualInsights?: boolean; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; - /** Optional page title for screen readers */ - pageTitle?: ReactNode; /** * Optional data source picker rendered in the page header. When provided it * replaces the static data view title. Typically a `DataDriftDataSourcePicker`- @@ -60,7 +58,6 @@ export const LogRateAnalysisAppState: FC = ({ appContextValue, showContextualInsights = false, showFrozenDataTierChoice = true, - pageTitle, headerContent, }) => { if (!dataView) { @@ -115,7 +112,6 @@ export const LogRateAnalysisAppState: FC = ({ diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx index 90ab3eb1d747a..237b5672e424d 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx @@ -58,13 +58,11 @@ interface SignificantFieldValue { interface LogRateAnalysisPageProps { showContextualInsights?: boolean; - pageTitle?: ReactNode; headerContent?: ReactNode; } export const LogRateAnalysisPage: FC = ({ showContextualInsights = false, - pageTitle, headerContent, }) => { const aiopsAppContext = useAiopsAppContext(); @@ -300,7 +298,7 @@ export const LogRateAnalysisPage: FC = ({ return ( - + diff --git a/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx b/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx index 96420daef28f5..c09fbee056f18 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/page_header/page_header.tsx @@ -9,7 +9,7 @@ import { css } from '@emotion/react'; import type { FC, ReactNode } from 'react'; import React, { useCallback, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiScreenReaderOnly, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { useUrlState } from '@kbn/ml-url-state'; import { useStorage } from '@kbn/ml-local-storage'; @@ -35,8 +35,6 @@ const maxInlineSizeStyles = css` `; export interface PageHeaderProps { - /** Screen-reader page title rendered as an h1 when `headerContent` is provided. */ - pageTitle?: ReactNode; /** Optional content rendered to the right of the header content */ rightSideItems?: ReactNode; /** @@ -46,7 +44,7 @@ export interface PageHeaderProps { headerContent?: ReactNode; } -export const PageHeader: FC = ({ pageTitle, rightSideItems, headerContent }) => { +export const PageHeader: FC = ({ rightSideItems, headerContent }) => { const [, setGlobalState] = useUrlState('_g'); const { dataView } = useDataSource(); @@ -85,17 +83,10 @@ export const PageHeader: FC = ({ pageTitle, rightSideItems, hea {headerContent !== undefined ? ( - <> - {pageTitle ? ( - -

{pageTitle}

-
- ) : null} - - {headerContent} - {rightSideItems ? {rightSideItems} : null} - - + + {headerContent} + {rightSideItems ? {rightSideItems} : null} + ) : (

{dataView.getName()}

diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx index 437a51c6534dd..f415ec0036003 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/change_point_detection.tsx @@ -14,11 +14,12 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { ChangePointDetection } from '@kbn/aiops-plugin/public'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import { useFieldStatsTrigger, FieldStatsFlyoutProvider } from '@kbn/ml-field-stats-flyout'; -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { MlDataSourcePicker } from '@kbn/aiops-components'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { NoDataViewPrompt } from './no_data_view_prompt'; import { useDataSource } from '../contexts/ml/data_source_context'; import { useMlKibana } from '../contexts/kibana'; import { HelpMenu } from '../components/help_menu'; @@ -64,31 +65,13 @@ export const ChangePointDetectionPage: FC = () => { {!dataView ? ( <> {headerContent} - - - - } - body={ -

- -

- } - /> + ) : ( { {!dataView ? ( <> {headerContent} - - - - } - body={ -

- -

- } - /> + ) : ( { {!dataView ? ( <> {headerContent} - - - - } - body={ -

- -

- } - /> + ) : ( { savedSearch={savedSearch} showContextualInsights={showContextualInsights} showFrozenDataTierChoice={showNodeInfo} - pageTitle={pageTitle} headerContent={headerContent} appContextValue={{ embeddingOrigin: AIOPS_EMBEDDABLE_ORIGIN.ML_AIOPS_LABS, diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx new file mode 100644 index 0000000000000..e2c9731a08306 --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx @@ -0,0 +1,32 @@ +/* + * 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 { EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const NoDataViewPrompt: FC = () => ( + + + + } + body={ +

+ +

+ } + /> +); From e6090fe3f3f3c7823d644b1d5702a7821d838720 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 25 May 2026 13:21:45 +0200 Subject: [PATCH 26/32] refactor(ml): remove headerContent handling from app state components - Eliminated the conditional rendering of `headerContent` in ChangePointDetection, LogCategorization, and LogRateAnalysis app state components, simplifying their logic. --- .../change_point_detection/change_point_detection_root.tsx | 7 ------- .../log_categorization/log_categorization_app_state.tsx | 7 ------- .../log_rate_analysis/log_rate_analysis_app_state.tsx | 7 ------- 3 files changed, 21 deletions(-) diff --git a/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx b/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx index 3f3d36edf5606..ccb6be3e6c90e 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/change_point_detection/change_point_detection_root.tsx @@ -97,13 +97,6 @@ export const ChangePointDetectionAppState: FC ); if (!dataView) { - if (headerContent !== undefined) { - return ( - - {headerContent} - - ); - } return null; } diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx index 9ee22e523ca2b..c836bedd2356b 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/log_categorization_app_state.tsx @@ -55,13 +55,6 @@ export const LogCategorizationAppState: FC = ({ headerContent, }) => { if (!dataView) { - if (headerContent !== undefined) { - return ( - - {headerContent} - - ); - } return null; } diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index f6147f42457ac..ad0c450437622 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -61,13 +61,6 @@ export const LogRateAnalysisAppState: FC = ({ headerContent, }) => { if (!dataView) { - if (headerContent !== undefined) { - return ( - - {headerContent} - - ); - } return null; } From bda4791d1188bad10a78b96e6a278579e86407e3 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Mon, 25 May 2026 16:50:36 +0200 Subject: [PATCH 27/32] refactor(ml): improve default data view handling in DataSourceContextProvider - Updated the logic in DataSourceContextProvider to retrieve the default data view using its ID instead of directly fetching the default data view to avoid setting defaul data view globally --- .../public/application/contexts/ml/data_source_context.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx index 6cb814b1de74e..808b2dac8f248 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx @@ -80,7 +80,10 @@ export const DataSourceContextProvider: FC> = ({ chil } else if (dataViewId !== undefined) { dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId); } else { - dataViewAndSavedSearch.dataView = await dataViews.getDefaultDataView().catch(() => null); + const defaultId = await dataViews.getDefaultId(); + if (defaultId) { + dataViewAndSavedSearch.dataView = await dataViews.get(defaultId).catch(() => null); + } } const { savedSearch, dataView } = dataViewAndSavedSearch; From e11dede8d1bd06158e7c31d0f753b3cc57032383 Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Tue, 26 May 2026 10:04:53 +0200 Subject: [PATCH 28/32] refactor(ml): revert ComboBoxService option retrieval --- .../test/functional/services/combo_box.ts | 21 ++++++------------- .../journeys_e2e/tsdb_logs_data_visualizer.ts | 3 ++- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/platform/test/functional/services/combo_box.ts b/src/platform/test/functional/services/combo_box.ts index 981d84f5d9e5e..b9cd3cd5e63a2 100644 --- a/src/platform/test/functional/services/combo_box.ts +++ b/src/platform/test/functional/services/combo_box.ts @@ -87,10 +87,7 @@ export class ComboBoxService extends FtrService { public async getOptions(comboBoxSelector: string) { const comboBoxElement = await this.testSubjects.find(comboBoxSelector); await this.openOptionsList(comboBoxElement); - return await this.find.allByCssSelector( - '.euiComboBoxOption, .euiFilterSelectItem', - this.WAIT_FOR_EXISTS_TIME - ); + return await this.find.allByCssSelector('.euiComboBoxOption', this.WAIT_FOR_EXISTS_TIME); } /** @@ -119,7 +116,7 @@ export class ComboBoxService extends FtrService { if (trimmedValue !== undefined) { const selectOptions = await this.find.allByCssSelector( - `.euiComboBoxOption[title="${trimmedValue}"], .euiFilterSelectItem[title="${trimmedValue}"]`, + `.euiComboBoxOption[title="${trimmedValue}"]`, this.WAIT_FOR_EXISTS_TIME ); @@ -130,10 +127,7 @@ export class ComboBoxService extends FtrService { const alternateTitle = ( await Promise.all( ( - await this.find.allByCssSelector( - `.euiComboBoxOption, .euiFilterSelectItem`, - this.WAIT_FOR_EXISTS_TIME - ) + await this.find.allByCssSelector(`.euiComboBoxOption`, this.WAIT_FOR_EXISTS_TIME) ).map(async (e) => { const title = (await e.getAttribute('title')) ?? ''; return { title, formattedTitle: title.toLowerCase().trim() }; @@ -145,7 +139,7 @@ export class ComboBoxService extends FtrService { const [alternate] = alternateTitle ? await this.find.allByCssSelector( - `.euiComboBoxOption[title="${alternateTitle}" i], .euiFilterSelectItem[title="${alternateTitle}" i]`, + `.euiComboBoxOption[title="${alternateTitle}" i]`, this.WAIT_FOR_EXISTS_TIME ) : []; @@ -160,15 +154,12 @@ export class ComboBoxService extends FtrService { this.log.warning( `comboBox.setElement - Could not find option [${trimmedValue}], using first` ); - const firstOption = await this.find.byCssSelector( - '.euiComboBoxOption, .euiFilterSelectItem', - 5000 - ); + const firstOption = await this.find.byCssSelector('.euiComboBoxOption', 5000); await this.clickOption(options.clickWithMouse, firstOption); } } } else { - const firstOption = await this.find.byCssSelector('.euiComboBoxOption, .euiFilterSelectItem'); + const firstOption = await this.find.byCssSelector('.euiComboBoxOption'); await this.clickOption(options.clickWithMouse, firstOption); } await this.closeOptionsList(comboBoxElement); diff --git a/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts index ba63259dc4b86..46e493f597459 100644 --- a/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts +++ b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts @@ -23,7 +23,7 @@ export const journey = new Journey({ .step('Go to Index data visualizer', async ({ page }) => { const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); await createButtons.first().click(); - await page.waitForSelector(subj('dataVisualizerIndexPage')); + await page.waitForSelector(subj('mlDataSourceSelectorButton')); }) .step('Go to Data View selection', async ({ page, kibanaPage }) => { await page.click(subj('mlDataSourceSelectorButton')); @@ -33,6 +33,7 @@ export const journey = new Journey({ .locator(subj('indexPattern-switcher')) .locator(`[title="${DATA_VIEW_NAME}"]`) .click(); + await page.waitForSelector(subj('dataVisualizerIndexPage'), { timeout: 60000 }); await page.click(subj('mlDatePickerButtonUseFullData')); await kibanaPage.waitForHeader(); await page.waitForSelector(subj('dataVisualizerTable-loaded'), { timeout: 60000 }); From ba0ff48ed2ce48aa95d2706264a46626cd4bd7aa Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Tue, 26 May 2026 11:16:53 +0200 Subject: [PATCH 29/32] refactor(ml): remove filterEsql prop from MlDataSourcePicker and MlOpenSessionFlyout - Eliminated the `filterEsql` prop from both `MlDataSourcePicker` and `MlOpenSessionFlyout` components to simplify their interfaces. - Updated the logic to filter out ES|QL-based saved searches directly within the `MlOpenSessionFlyout` --- .../ml_data_source_picker.tsx | 4 ---- .../ml_open_session_flyout.tsx | 22 ++++++------------- .../private/ml/aiops_components/tsconfig.json | 1 + .../index_based/index_data_visualizer.tsx | 1 - 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx index 14583951cbc46..52da930b5164e 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -42,8 +42,6 @@ export interface MlDataSourcePickerProps { services: MlDataSourcePickerServices; DataViewPickerComponent: ComponentType; SavedObjectFinderComponent: MlOpenSessionFlyoutProps['SavedObjectFinderComponent']; - /** When true, ES|QL-based sessions are hidden from the session picker */ - filterEsql?: boolean; /** Called after a field is saved via the field editor */ onFieldSaved?: () => void; } @@ -55,7 +53,6 @@ export const MlDataSourcePicker: FC = ({ services, DataViewPickerComponent, SavedObjectFinderComponent, - filterEsql = false, onFieldSaved, }) => { const [savedDataViews, setSavedDataViews] = useState([]); @@ -189,7 +186,6 @@ export const MlDataSourcePicker: FC = ({ onClose={onCloseSession} onOpenSavedSearch={onOpenSavedSearch} SavedObjectFinderComponent={SavedObjectFinderComponent} - filterEsql={filterEsql} /> ) : ( diff --git a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx index df7f2f53dda52..bbe38a10aa134 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -21,15 +21,12 @@ import { EuiTitle, useGeneratedHtmlId, } from '@elastic/eui'; -import { - SavedSearchType, - SavedSearchTypeDisplayName, - type SavedSearchAttributes, -} from '@kbn/saved-search-plugin/common'; +import { SavedSearchType, SavedSearchTypeDisplayName } from '@kbn/saved-search-plugin/common'; import type { ApplicationStart, IUiSettingsClient } from '@kbn/core/public'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import type { SavedObjectFinderProps } from '@kbn/saved-objects-finder-plugin/public'; -import type { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; +import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; +import { isEsqlSavedSearch, type DiscoverSessionFinderAttributes } from '@kbn/discover-utils'; export type { SavedObjectFinderProps }; @@ -49,8 +46,6 @@ export interface MlOpenSessionFlyoutProps { onClose: () => void; onOpenSavedSearch: (id: string) => void; SavedObjectFinderComponent: ComponentType; - /** When true, ES|QL-based sessions are hidden from the list */ - filterEsql?: boolean; } export const MlOpenSessionFlyout: FC = ({ @@ -58,7 +53,6 @@ export const MlOpenSessionFlyout: FC = ({ onClose, onOpenSavedSearch, SavedObjectFinderComponent, - filterEsql = false, }) => { const modalTitleId = useGeneratedHtmlId(); const { http, application, contentManagement, uiSettings } = services; @@ -108,12 +102,10 @@ export const MlOpenSessionFlyout: FC = ({ defaultMessage: 'Discover session', } ), - ...(filterEsql - ? { - showSavedObject: (savedObject: SavedObjectCommon) => - !savedObject.attributes.isTextBasedQuery, - } - : {}), + // ES|QL Based saved searches are not supported in Discover sessions, filter them out + showSavedObject: ( + savedObject: SavedObjectCommon + ) => !isEsqlSavedSearch(savedObject), }, ]} onChoose={onOpenSavedSearch} diff --git a/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json b/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json index b4cca5161340f..b7fc4e405d629 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json +++ b/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json @@ -34,6 +34,7 @@ "@kbn/saved-search-plugin", "@kbn/shared-ux-utility", "@kbn/unified-search-plugin", + "@kbn/discover-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 8e4d2e40ff137..bfd4d80b11654 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -204,7 +204,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) services={services} DataViewPickerComponent={DataViewPicker} SavedObjectFinderComponent={SavedObjectFinder} - filterEsql onFieldSaved={() => mlTimefilterRefresh$.next({ lastRefresh: Date.now() })} /> ) : undefined; From 13f3d46135169aaee9168f930132b68b47da14ca Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 26 May 2026 10:12:33 +0000 Subject: [PATCH 30/32] Changes from node scripts/regenerate_moon_projects.js --update --- x-pack/platform/packages/private/ml/aiops_components/moon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/packages/private/ml/aiops_components/moon.yml b/x-pack/platform/packages/private/ml/aiops_components/moon.yml index 1f37649b2d0c1..ea4ddeaef5453 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/moon.yml +++ b/x-pack/platform/packages/private/ml/aiops_components/moon.yml @@ -32,6 +32,7 @@ dependsOn: - '@kbn/saved-search-plugin' - '@kbn/shared-ux-utility' - '@kbn/unified-search-plugin' + - '@kbn/discover-utils' tags: - shared-common - package From b7c66f95b77a2f18653bc4160c4e69192579c6ad Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Tue, 26 May 2026 15:13:13 +0200 Subject: [PATCH 31/32] refactor(ml): enhance data view retrieval logic in DataSourceContextProvider - Improved the logic for retrieving data views in DataSourceContextProvider by introducing checks for saved search and data view IDs. - Added fallback mechanisms to ensure a data view is always available, including retrieving patterns when no default data view is found. - Streamlined the handling of default data views to enhance reliability and maintainability. --- .../contexts/ml/data_source_context.test.tsx | 41 ++++++++++--------- .../contexts/ml/data_source_context.tsx | 24 +++++++++-- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx index ddd0aa71c0388..5511514c77b2f 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.tsx @@ -32,7 +32,8 @@ jest.mock('react-router-dom', () => ({ })); const mockGet = jest.fn(); -const mockGetDefaultDataView = jest.fn(); +const mockGetDefaultId = jest.fn(); +const mockGetIdsWithTitle = jest.fn(); const mockGetDataViewAndSavedSearch = jest.fn(); const buildKibanaMock = () => ({ @@ -40,7 +41,8 @@ const buildKibanaMock = () => ({ data: { dataViews: { get: mockGet, - getDefaultDataView: mockGetDefaultDataView, + getDefaultId: mockGetDefaultId, + getIdsWithTitle: mockGetIdsWithTitle, }, }, savedSearch: mockGetDataViewAndSavedSearch, @@ -59,14 +61,13 @@ describe('DataSourceContextProvider', () => { beforeEach(() => { jest.clearAllMocks(); (useMlKibana as jest.Mock).mockReturnValue(buildKibanaMock()); + mockGetIdsWithTitle.mockResolvedValue([]); }); it('uses default data view when no URL params are present', async () => { mockLocationSearch.mockReturnValue(''); - mockGetDefaultDataView.mockResolvedValue({ - id: 'default-dv', - title: 'Default Data View', - }); + mockGetDefaultId.mockResolvedValue('default-dv'); + mockGet.mockResolvedValue({ id: 'default-dv', title: 'Default Data View' }); renderProvider(); @@ -74,16 +75,14 @@ describe('DataSourceContextProvider', () => { expect(screen.getByTestId('child-content')).toBeInTheDocument(); }); - expect(mockGetDefaultDataView).toHaveBeenCalled(); + expect(mockGetDefaultId).toHaveBeenCalled(); + expect(mockGet).toHaveBeenCalledWith('default-dv'); }); - it('uses managed default data view directly without falling back to list', async () => { + it('uses default data view when it has an id returned by getDefaultId', async () => { mockLocationSearch.mockReturnValue(''); - mockGetDefaultDataView.mockResolvedValue({ - id: 'logs-star', - title: 'logs-*', - managed: true, - }); + mockGetDefaultId.mockResolvedValue('logs-star'); + mockGet.mockResolvedValue({ id: 'logs-star', title: 'logs-*' }); renderProvider(); @@ -91,12 +90,13 @@ describe('DataSourceContextProvider', () => { expect(screen.getByTestId('child-content')).toBeInTheDocument(); }); - expect(mockGetDefaultDataView).toHaveBeenCalled(); + expect(mockGetDefaultId).toHaveBeenCalled(); + expect(mockGet).toHaveBeenCalledWith('logs-star'); }); - it('renders children with null data view when getDefaultDataView throws', async () => { + it('renders children with null data view when getDefaultId throws', async () => { mockLocationSearch.mockReturnValue(''); - mockGetDefaultDataView.mockRejectedValue(new Error('No default data view')); + mockGetDefaultId.mockRejectedValue(new Error('No default data view')); renderProvider(); @@ -104,13 +104,13 @@ describe('DataSourceContextProvider', () => { expect(screen.getByTestId('child-content')).toBeInTheDocument(); }); - expect(mockGetDefaultDataView).toHaveBeenCalled(); + expect(mockGetDefaultId).toHaveBeenCalled(); expect(mockGet).not.toHaveBeenCalled(); }); it('renders children with null data view when no default and no data views exist', async () => { mockLocationSearch.mockReturnValue(''); - mockGetDefaultDataView.mockResolvedValue(null); + mockGetDefaultId.mockResolvedValue(null); renderProvider(); @@ -171,18 +171,19 @@ describe('DataSourceContextProvider', () => { it('renders null initially before the async resolve completes', async () => { mockLocationSearch.mockReturnValue(''); let resolvePromise: (value: any) => void; - mockGetDefaultDataView.mockReturnValue( + mockGetDefaultId.mockReturnValue( new Promise((resolve) => { resolvePromise = resolve; }) ); + mockGet.mockResolvedValue({ id: 'default-dv', title: 'Default' }); const { container } = renderProvider(); expect(container.firstChild).toBeNull(); await waitFor(() => { - resolvePromise!({ id: 'default-dv', title: 'Default' }); + resolvePromise!('default-dv'); }); await waitFor(() => { diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx index 808b2dac8f248..0860eed069327 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx @@ -75,17 +75,33 @@ export const DataSourceContextProvider: FC> = ({ chil let dataViewAndSavedSearch: DataViewAndSavedSearch = { savedSearch: null, dataView: null }; - if (savedSearchId !== undefined) { + const hasSavedSearchId = savedSearchId !== undefined; + const hasDataViewId = dataViewId !== undefined; + const shouldUseDefaultDataView = !hasSavedSearchId && !hasDataViewId; + + if (hasSavedSearchId) { dataViewAndSavedSearch = await getDataViewAndSavedSearchCb(savedSearchId); - } else if (dataViewId !== undefined) { + } + + if (!hasSavedSearchId && hasDataViewId) { dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId); - } else { - const defaultId = await dataViews.getDefaultId(); + } + + if (shouldUseDefaultDataView) { + const defaultId = await dataViews.getDefaultId().catch(() => null); if (defaultId) { dataViewAndSavedSearch.dataView = await dataViews.get(defaultId).catch(() => null); } } + if (shouldUseDefaultDataView && !dataViewAndSavedSearch.dataView) { + const patterns = await dataViews.getIdsWithTitle(); + const fallbackId = patterns[0]?.id; + if (fallbackId) { + dataViewAndSavedSearch.dataView = await dataViews.get(fallbackId).catch(() => null); + } + } + const { savedSearch, dataView } = dataViewAndSavedSearch; const { combinedQuery } = createSearchItems( From 1ba583e9f421b7821d38f95e005c633f2ef2e06b Mon Sep 17 00:00:00 2001 From: Konrad Krasocki Date: Sat, 30 May 2026 13:36:25 +0200 Subject: [PATCH 32/32] refactor(ml): unify no data view messages i18n ids --- .../ml/public/application/aiops/no_data_view_prompt.tsx | 4 ++-- .../application/datavisualizer/data_drift/data_drift_page.tsx | 4 ++-- .../datavisualizer/index_based/index_data_visualizer.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx b/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx index e2c9731a08306..d0fa94cd17b31 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/aiops/no_data_view_prompt.tsx @@ -15,7 +15,7 @@ export const NoDataViewPrompt: FC = () => ( title={

@@ -23,7 +23,7 @@ export const NoDataViewPrompt: FC = () => ( body={

diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx index b81cc112043a0..3f71855c5f0bc 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/data_drift/data_drift_page.tsx @@ -66,7 +66,7 @@ export const DataDriftPage: FC = () => { title={

@@ -74,7 +74,7 @@ export const DataDriftPage: FC = () => { body={

diff --git a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index bfd4d80b11654..5859aa26e0cda 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -236,7 +236,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) title={

@@ -244,7 +244,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) body={