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/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/performance/journeys_e2e/tsdb_logs_data_visualizer.ts b/x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts index 5bc7aac048438..46e493f597459 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,20 @@ export const journey = new Journey({ await page.waitForSelector(subj('mlDataVisualizerCardIndexData')); await page.waitForSelector(subj('globalLoadingIndicator-hidden')); }) - .step('Go to data view selection', async ({ page }) => { + .step('Go to Index data visualizer', async ({ page }) => { const createButtons = page.locator(subj('mlDataVisualizerSelectIndexButton')); await createButtons.first().click(); - await page.waitForSelector(subj('savedObjectsFinderTable')); + await page.waitForSelector(subj('mlDataSourceSelectorButton')); }) - .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.waitForSelector(subj('dataVisualizerIndexPage'), { timeout: 60000 }); await page.click(subj('mlDatePickerButtonUseFullData')); await kibanaPage.waitForHeader(); await page.waitForSelector(subj('dataVisualizerTable-loaded'), { timeout: 60000 }); 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/moon.yml b/x-pack/platform/packages/private/ml/aiops_components/moon.yml index e0327bc000539..ea4ddeaef5453 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,13 @@ 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' + - '@kbn/discover-utils' tags: - shared-common - package 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/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..aaff44caa849b --- /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,215 @@ +/* + * 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'; + +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: '' })), +})); + +let capturedDataViewPickerProps: Record = {}; +const MockDataViewPicker = (props: any) => { + capturedDataViewPickerProps = props; + return ( +
+ {props.trigger?.label} +
+ ); +}; + +jest.mock('./ml_open_session_flyout', () => ({ + MlOpenSessionFlyout: (props: any) => { + return ( +
+ + +
+ ); + }, +})); + +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(mockHistoryReplace).toHaveBeenCalledWith({ search: '?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(mockHistoryReplace).toHaveBeenCalledWith({ + search: '?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(mockHistoryReplace).toHaveBeenCalledWith({ search: '?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/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 new file mode 100644 index 0000000000000..52da930b5164e --- /dev/null +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_data_source_picker.tsx @@ -0,0 +1,194 @@ +/* + * 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 { ComponentType, FC } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +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'; +import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; + +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; + DataViewPickerComponent: ComponentType; + SavedObjectFinderComponent: MlOpenSessionFlyoutProps['SavedObjectFinderComponent']; + /** Called after a field is saved via the field editor */ + onFieldSaved?: () => void; +} + +const dataViewPickerStyles = css({ minWidth: 180 }); + +export const MlDataSourcePicker: FC = ({ + currentDataView, + services, + DataViewPickerComponent, + SavedObjectFinderComponent, + onFieldSaved, +}) => { + 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>(); + + useEffect(() => { + return () => { + closeFieldEditorRef.current?.(); + }; + }, []); + + useEffect(() => { + 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) => { + updateDataSource('index', id); + }, + [updateDataSource] + ); + + const onDataViewCreated = useCallback( + (created: DataView) => { + if (created.id) { + dataViews.getIdsWithTitle().then(setSavedDataViews); + updateDataSource('index', created.id); + } + }, + [dataViews, updateDataSource] + ); + + const onOpenSavedSearch = useCallback( + (id: string) => { + setOpenSessionPanelVisible(false); + updateDataSource('savedSearchId', id); + }, + [updateDataSource] + ); + + const canEditDataView = useMemo( + () => Boolean(dataViewEditor?.userPermissions.editDataView()), + [dataViewEditor] + ); + + const onAddField = useMemo( + () => + canEditDataView && currentDataView + ? async () => { + closeFieldEditorRef.current = await dataViewFieldEditor.openEditor({ + ctx: { dataView: currentDataView }, + onSave: () => { + dataViews.getIdsWithTitle().then(setSavedDataViews); + onFieldSaved?.(); + }, + }); + } + : undefined, + [canEditDataView, currentDataView, dataViewFieldEditor, dataViews, onFieldSaved] + ); + + const triggerLabel = useMemo( + () => + currentDataView?.getName() ?? + i18n.translate('xpack.aiops.mlDataSourcePicker.selectDataViewLabel', { + defaultMessage: 'Select data view', + }), + [currentDataView] + ); + + const onOpenSession = useCallback(() => setOpenSessionPanelVisible(true), []); + + const onCloseSession = useCallback(() => setOpenSessionPanelVisible(false), []); + + const dataViewPickerContent = ( + + + + + + + + + + + ); + + return isOpenSessionPanelVisible ? ( + <> + {dataViewPickerContent} + + + ) : ( + dataViewPickerContent + ); +}; 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 new file mode 100644 index 0000000000000..bbe38a10aa134 --- /dev/null +++ b/x-pack/platform/packages/private/ml/aiops_components/src/ml_data_source_picker/ml_open_session_flyout.tsx @@ -0,0 +1,142 @@ +/* + * 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 { ComponentType, 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 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 { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; +import { isEsqlSavedSearch, type DiscoverSessionFinderAttributes } from '@kbn/discover-utils'; + +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 { http, application, contentManagement, uiSettings } = services; + + const hasSavedObjectPermission = + application.capabilities.savedObjectsManagement?.edit || + application.capabilities.savedObjectsManagement?.delete; + + return ( + + + +

+ +

+
+
+ + + } + savedObjectMetaData={[ + { + type: SavedSearchType, + getIconForSavedObject: () => 'discoverApp', + name: i18n.translate( + 'xpack.aiops.dataSourcePicker.openSessionFlyout.savedObjectName', + { + defaultMessage: 'Discover session', + } + ), + // ES|QL Based saved searches are not supported in Discover sessions, filter them out + showSavedObject: ( + savedObject: SavedObjectCommon + ) => !isEsqlSavedSearch(savedObject), + }, + ]} + onChoose={onOpenSavedSearch} + 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/packages/private/ml/aiops_components/tsconfig.json b/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json index 098fc65087afc..b7fc4e405d629 100644 --- a/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json +++ b/x-pack/platform/packages/private/ml/aiops_components/tsconfig.json @@ -28,6 +28,13 @@ "@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", + "@kbn/discover-utils", ], "exclude": [ "target/**/*", 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..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,13 +66,11 @@ 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 | 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/packages/shared/ml/common-types/locator_ml_pages.ts b/x-pack/platform/packages/shared/ml/common-types/locator_ml_pages.ts index a0a870276c4d8..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 @@ -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', @@ -27,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/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..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 @@ -5,7 +5,8 @@ * 2.0. */ -import type { FC } from 'react'; +import { css } from '@emotion/react'; +import type { FC, ReactNode } from 'react'; import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; import type { estypes } from '@elastic/elasticsearch'; @@ -16,9 +17,9 @@ import { EuiPageSection, EuiPanel, EuiSpacer, - EuiPageHeader, EuiHorizontalRule, EuiBadge, + EuiTitle, } from '@elastic/eui'; import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; @@ -35,7 +36,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 +57,17 @@ 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', -}); +const maxInlineSizeStyles = css` + max-inline-size: 100%; + min-inline-size: 0; +`; 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 +103,47 @@ export const PageHeader: FC = ({ onRefresh, needsUpdate }) => { ); return ( - - {dataView.getName()} -
- } - rightSideGroupProps={{ - gutterSize: 's', - 'data-test-subj': 'dataComparisonTimeRangeSelectorSection', - }} - rightSideItems={[ - , - hasValidTimeField && ( - - ), - ].filter(Boolean)} - /> + + + {headerContent ?? ( + +

{dataView.getName()}

+
+ )} +
+ + + {hasValidTimeField && ( + + + + )} + + + + + +
); }; @@ -147,12 +159,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 +417,11 @@ export const DataDriftPage: FC = ({ initialSettings }) => { return ( - + diff --git a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx b/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx deleted file mode 100644 index 4e46420bf737f..0000000000000 --- a/x-pack/platform/plugins/private/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx +++ /dev/null @@ -1,125 +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, { useEffect, useRef, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import type { Refresh } from '@kbn/ml-date-picker'; -import { mlTimefilterRefresh$ } from '@kbn/ml-date-picker'; -import { useDataVisualizerKibana } from '../../../kibana_context'; - -export interface DataVisualizerDataViewManagementProps { - /** - * Currently selected data view - */ - currentDataView?: DataView; -} - -export function DataVisualizerDataViewManagement(props: DataVisualizerDataViewManagementProps) { - const { - services: { dataViewFieldEditor, application }, - } = useDataVisualizerKibana(); - - const { currentDataView } = props; - const dataViewFieldEditPermission = dataViewFieldEditor?.userPermissions.editIndexPattern(); - const canEditDataViewField = !!dataViewFieldEditPermission; - const [isAddDataViewFieldPopoverOpen, setIsAddDataViewFieldPopoverOpen] = useState(false); - - const closeFieldEditor = useRef<() => 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 763b13713b4e6..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 @@ -6,13 +6,12 @@ */ 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, @@ -64,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 { @@ -74,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: {}, }; @@ -104,9 +107,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 +465,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 +520,40 @@ 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..f6d3c3437290c 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(); @@ -280,12 +287,16 @@ const DataVisualizerStateContextProvider: FC {currentDataView ? ( - ) : null} + ) : ( + headerContent ?? null + )} ); }; @@ -294,12 +305,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 +365,7 @@ export const IndexDataVisualizer: FC = ({ ) : ( 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 a2083e465872c..a0c9c8630ad4a 100644 --- a/x-pack/platform/plugins/private/translations/translations/de-DE.json +++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json @@ -17706,9 +17706,6 @@ "xpack.dataVisualizer.index.dataGrid.exploreInMapsTitle": "In Maps erkunden", "xpack.dataVisualizer.index.dataLoader.internalServerErrorMessage": "Fehler beim Laden der Daten im Index {index}. {message}. Die Anfrage ist möglicherweise abgelaufen. Versuchen Sie, eine kleinere Stichprobengröße zu verwenden oder den Zeitraum einzugrenzen.", "xpack.dataVisualizer.index.dataViewErrorMessage": "Fehler beim Suchen der Datenansicht", - "xpack.dataVisualizer.index.dataViewManagement.actionsPopoverLabel": "Einstellungen für Datenansicht", - "xpack.dataVisualizer.index.dataViewManagement.addFieldButton": "Feld zur Datenansicht hinzufügen", - "xpack.dataVisualizer.index.dataViewManagement.manageFieldButton": "Datenansichtsfelder verwalten", "xpack.dataVisualizer.index.embeddableErrorDescription": "Beim Laden des einbettbaren Elements ist ein Fehler aufgetreten. Bitte überprüfen Sie, ob alle erforderlichen Eingänge gültig sind.", "xpack.dataVisualizer.index.embeddableErrorTitle": "Fehler beim Laden von einbettbaren Inhalten", "xpack.dataVisualizer.index.embeddableNoResultsMessage": "Keine Ergebnisse gefunden", @@ -29084,15 +29081,10 @@ "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.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}.", @@ -29563,7 +29555,6 @@ "xpack.ml.customUrlsEditor.urlLabel": "URL", "xpack.ml.customUrlsList.invalidIntervalFormatErrorMessage": "Ungültiges Intervallformat", "xpack.ml.dataDrift.createDataDriftDataViewTitle": "Datenansicht erstellen und Datendrift analysieren", - "xpack.ml.dataDrift.createDataViewButton": "Erstellen Sie eine Datenansicht", "xpack.ml.dataDrift.indexPatternsEditor.additionalSettingsTitle": "Zusätzliche Einstellungen", "xpack.ml.dataDrift.indexPatternsEditor.allIndicesLabel": "{indicesLength, plural, one {# source} other {# sources} }", "xpack.ml.dataDrift.indexPatternsEditor.analyzeDataDriftLabel": "Analysieren von Datenabweichungen", @@ -29585,7 +29576,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", @@ -30116,7 +30106,6 @@ "xpack.ml.dataViewUtils.dataViewTimeFieldLabel": "Zeitfeld für die Datenansicht", "xpack.ml.dataViewUtils.dataViewTitleError": "Eine Datenansicht mit diesem Titel existiert bereits.", "xpack.ml.dataViewUtils.noTimeFieldOptionLabel": "Ich möchte die Zeitfeldoption nicht verwenden", - "xpack.ml.datavisualizer": "(ES|QL)", "xpack.ml.dataVisualizer.dataDrift.docTitle": "Datendrift", "xpack.ml.dataVisualizer.dataDriftCustomIndexPatterns.docTitle": "Benutzerdefinierte Daten-Indexmuster für Datendrift", "xpack.ml.dataVisualizer.dataView.docTitle": "Index-Daten-Visualizer", @@ -30139,7 +30128,6 @@ "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "Verwenden Sie die Pipe-basierte Abfragesprache von Elastic, um die Form von Daten in einem Elasticsearch-Index zu analysieren.", "xpack.ml.datavisualizer.selector.tryESQLNowButtonLabel": "ES|QL-Editor öffnen", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "Datendateien hochladen", - "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "Verwenden Sie ES|QL", "xpack.ml.datavisualizer.startTrial.fullMLFeaturesDescription": "Um die vollständigen Machine Learning Funktionen, die ein {subscriptionsLink} bietet, zu nutzen, starten Sie einfach eine 30-tägige Testversion.", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "Platinum- oder Enterprise-Abonnement", "xpack.ml.datavisualizerBreadcrumbLabel": "Data Visualizer", @@ -30494,7 +30482,6 @@ "xpack.ml.jobsBreadcrumbs.multiMetricLabel": "Multi-Metrik", "xpack.ml.jobsBreadcrumbs.populationLabel": "Population", "xpack.ml.jobsBreadcrumbs.rareLabel": "Selten", - "xpack.ml.jobsBreadcrumbs.selectDateViewLabel": "Datenansicht auswählen", "xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabelRecognize": "Anerkannter Index", "xpack.ml.jobsBreadcrumbs.selectJobType": "Job erstellen", "xpack.ml.jobsBreadcrumbs.singleMetricLabel": "Single Metric", @@ -31744,7 +31731,6 @@ "xpack.ml.ruleEditor.typicalAppliesTypeText": "Typisch", "xpack.ml.sampleDataLinkLabel": "ML-Jobs", "xpack.ml.savedObjectFinder.createADataView": "Eine Datenansicht erstellen", - "xpack.ml.selectDataViewLabel": "Datenansicht auswählen", "xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "Sie haben {calendarsCountBadge} {calendarsDstCount, plural, one {calendar} other {Kalender}}", "xpack.ml.settings.anomalyDetection.calendarsDstText": "DST-Kalender enthalten eine Liste geplanter Ereignisse, für die Sie keine Anomalien erzeugen möchten, wobei Sommerzeitverschiebungen berücksichtigt werden, die dazu führen können, dass Ereignisse eine Stunde früher oder später stattfinden.", "xpack.ml.settings.anomalyDetection.calendarsDstTitle": "DST-Kalender", @@ -32391,7 +32377,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 32dcb5b83bf01..982bdfe845c67 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -17671,9 +17671,6 @@ "xpack.dataVisualizer.index.dataGrid.exploreInMapsTitle": "Explorer dans Maps", "xpack.dataVisualizer.index.dataLoader.internalServerErrorMessage": "Erreur lors du chargement des données dans l'index {index}. {message}. La requête a peut-être expiré. Essayez d'utiliser un échantillon d'une taille inférieure ou de réduire la plage temporelle.", "xpack.dataVisualizer.index.dataViewErrorMessage": "Erreur lors de la recherche de la vue de données", - "xpack.dataVisualizer.index.dataViewManagement.actionsPopoverLabel": "Paramètres Vue de données", - "xpack.dataVisualizer.index.dataViewManagement.addFieldButton": "Ajouter un champ à la vue de données", - "xpack.dataVisualizer.index.dataViewManagement.manageFieldButton": "Gérer les champs de la vue de données", "xpack.dataVisualizer.index.embeddableErrorDescription": "Une erreur s'est produite lors du chargement de l'incorporable. Veuillez vérifier si toutes les entrées requises sont valides.", "xpack.dataVisualizer.index.embeddableErrorTitle": "Erreur lors du chargement de l'incorporable", "xpack.dataVisualizer.index.embeddableNoResultsMessage": "Résultat introuvable", @@ -29038,15 +29035,10 @@ "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.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 :", @@ -29515,7 +29507,6 @@ "xpack.ml.customUrlsEditor.urlLabel": "URL", "xpack.ml.customUrlsList.invalidIntervalFormatErrorMessage": "Format d'intervalle non valide", "xpack.ml.dataDrift.createDataDriftDataViewTitle": "Créer une vue de données et analyser la dérive de données", - "xpack.ml.dataDrift.createDataViewButton": "Créer une vue de données", "xpack.ml.dataDrift.indexPatternsEditor.additionalSettingsTitle": "Paramètres supplémentaires", "xpack.ml.dataDrift.indexPatternsEditor.allIndicesLabel": "{indicesLength, plural, one {# source} other {# sources} }", "xpack.ml.dataDrift.indexPatternsEditor.analyzeDataDriftLabel": "Analyser la dérive de données", @@ -29537,7 +29528,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", @@ -30066,7 +30056,6 @@ "xpack.ml.dataViewUtils.dataViewTimeFieldLabel": "Champ temporel pour la vue de données", "xpack.ml.dataViewUtils.dataViewTitleError": "Une vue de données avec ce titre existe déjà.", "xpack.ml.dataViewUtils.noTimeFieldOptionLabel": "Je ne souhaite pas utiliser l'option de champ temporel", - "xpack.ml.datavisualizer": "(ES|QL)", "xpack.ml.dataVisualizer.dataDrift.docTitle": "Dérive de données", "xpack.ml.dataVisualizer.dataDriftCustomIndexPatterns.docTitle": "Modèles d'indexation personnalisés de dérive de données", "xpack.ml.dataVisualizer.dataView.docTitle": "Index Data Visualizer (Visualiseur de données pour les index)", @@ -30089,7 +30078,6 @@ "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "Utilisez le langage de requête pipé d'Elastic pour analyser la structure des données dans un index Elasticsearch.", "xpack.ml.datavisualizer.selector.tryESQLNowButtonLabel": "Ouvrir l'éditeur ES|QL", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "Charger les fichiers de données", - "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "Utiliser ES|QL", "xpack.ml.datavisualizer.startTrial.fullMLFeaturesDescription": "Pour profiter des fonctionnalités complètes de Machine Learning offertes par un {subscriptionsLink}, démarrez un essai de 30 jours.", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "Abonnement Platinum ou Enterprise", "xpack.ml.datavisualizerBreadcrumbLabel": "Visualiseur de données", @@ -30436,7 +30424,6 @@ "xpack.ml.jobsBreadcrumbs.multiMetricLabel": "À plusieurs indicateurs", "xpack.ml.jobsBreadcrumbs.populationLabel": "Population", "xpack.ml.jobsBreadcrumbs.rareLabel": "Rare", - "xpack.ml.jobsBreadcrumbs.selectDateViewLabel": "Sélectionner la vue de données", "xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabelRecognize": "Index reconnu", "xpack.ml.jobsBreadcrumbs.selectJobType": "Créer une tâche", "xpack.ml.jobsBreadcrumbs.singleMetricLabel": "Indicateur unique", @@ -31678,7 +31665,6 @@ "xpack.ml.ruleEditor.typicalAppliesTypeText": "typique", "xpack.ml.sampleDataLinkLabel": "Tâches de ML", "xpack.ml.savedObjectFinder.createADataView": "Créer une vue de données", - "xpack.ml.selectDataViewLabel": "Sélectionner la vue de données", "xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "Vous avez {calendarsCountBadge} {calendarsDstCount, plural, one {calendar} other {calendriers}}", "xpack.ml.settings.anomalyDetection.calendarsDstText": "Les calendriers DST contiennent une liste d'événements programmés pour lesquels vous ne souhaitez pas générer d'anomalies, en tenant compte des décalages de l'heure d'été qui peuvent entraîner l'apparition d'événements une heure plus tôt ou plus tard.", "xpack.ml.settings.anomalyDetection.calendarsDstTitle": "Calendriers DST", @@ -32328,7 +32314,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 566e0adf5d809..3a9760e325831 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -17765,9 +17765,6 @@ "xpack.dataVisualizer.index.dataGrid.exploreInMapsTitle": "Mapsで検索", "xpack.dataVisualizer.index.dataLoader.internalServerErrorMessage": "インデックス {index} のデータの読み込み中にエラーが発生。{message}。リクエストがタイムアウトした可能性があります。小さなサンプルサイズを使うか、時間範囲を狭めてみてください。", "xpack.dataVisualizer.index.dataViewErrorMessage": "データビューの検索エラー", - "xpack.dataVisualizer.index.dataViewManagement.actionsPopoverLabel": "データビュー設定", - "xpack.dataVisualizer.index.dataViewManagement.addFieldButton": "フィールドをデータビューに追加", - "xpack.dataVisualizer.index.dataViewManagement.manageFieldButton": "データビューフィールドを管理", "xpack.dataVisualizer.index.embeddableErrorDescription": "埋め込み可能オブジェクトの読み込みエラーが発生しました。すべての必須入力が有効であるかどうかを確認してください。", "xpack.dataVisualizer.index.embeddableErrorTitle": "埋め込み可能オブジェクトの読み込みエラー", "xpack.dataVisualizer.index.embeddableNoResultsMessage": "結果が見つかりませんでした", @@ -29184,15 +29181,10 @@ "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.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}.", @@ -29665,7 +29657,6 @@ "xpack.ml.customUrlsEditor.urlLabel": "URL", "xpack.ml.customUrlsList.invalidIntervalFormatErrorMessage": "無効な間隔のフォーマット", "xpack.ml.dataDrift.createDataDriftDataViewTitle": "データビューを作成し、データドリフトを分析", - "xpack.ml.dataDrift.createDataViewButton": "データビューを作成", "xpack.ml.dataDrift.indexPatternsEditor.additionalSettingsTitle": "追加設定", "xpack.ml.dataDrift.indexPatternsEditor.allIndicesLabel": "{indicesLength, plural, one {# source} other {# sources} }", "xpack.ml.dataDrift.indexPatternsEditor.analyzeDataDriftLabel": "データドリフトを分析", @@ -29687,7 +29678,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": "データセット全体で正規化された混同行列", @@ -30218,7 +30208,6 @@ "xpack.ml.dataViewUtils.dataViewTimeFieldLabel": "データビューの時間フィールド", "xpack.ml.dataViewUtils.dataViewTitleError": "このタイトルのデータビューはすでに存在します。", "xpack.ml.dataViewUtils.noTimeFieldOptionLabel": "時間フィールドオプションを使用しない", - "xpack.ml.datavisualizer": "(ES|QL)", "xpack.ml.dataVisualizer.dataDrift.docTitle": "データドリフト", "xpack.ml.dataVisualizer.dataDriftCustomIndexPatterns.docTitle": "データドリフトカスタムインデックスパターン", "xpack.ml.dataVisualizer.dataView.docTitle": "インデックスデータビジュアライザー", @@ -30241,7 +30230,6 @@ "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "Elasticsearchインデックス内のデータの形状を分析するには、Elasticのパイプクエリ言語を使用してください。", "xpack.ml.datavisualizer.selector.tryESQLNowButtonLabel": "ES|QLエディタを開く", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "データファイルをアップロード", - "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "ES|QLを使用", "xpack.ml.datavisualizer.startTrial.fullMLFeaturesDescription": "{subscriptionsLink}が提供するすべての機械学習機能を体験するには、30日間のトライアルを開始してください。", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "PlatinumまたはEnterprise サブスクリプション", "xpack.ml.datavisualizerBreadcrumbLabel": "Data Visualizer", @@ -30598,7 +30586,6 @@ "xpack.ml.jobsBreadcrumbs.multiMetricLabel": "マルチメトリック", "xpack.ml.jobsBreadcrumbs.populationLabel": "集団", "xpack.ml.jobsBreadcrumbs.rareLabel": "ほとんどない", - "xpack.ml.jobsBreadcrumbs.selectDateViewLabel": "データビューを選択", "xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabelRecognize": "認識されたインデックス", "xpack.ml.jobsBreadcrumbs.selectJobType": "ジョブを作成", "xpack.ml.jobsBreadcrumbs.singleMetricLabel": "シングルメトリック", @@ -31852,7 +31839,6 @@ "xpack.ml.ruleEditor.typicalAppliesTypeText": "通常", "xpack.ml.sampleDataLinkLabel": "ML ジョブ", "xpack.ml.savedObjectFinder.createADataView": "データビューを作成", - "xpack.ml.selectDataViewLabel": "データビューを選択", "xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "{calendarsCountBadge}{calendarsDstCount, plural, one {calendar} other {個のカレンダー}}があります", "xpack.ml.settings.anomalyDetection.calendarsDstText": "DSTカレンダーには、異常を生成すべきではないスケジュールされたイベントのリストが含まれています。夏時間の時差によりイベントが1時間早くなったり遅くなったりする可能性が考慮されています。", "xpack.ml.settings.anomalyDetection.calendarsDstTitle": "DSTカレンダー", @@ -32505,7 +32491,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 4f1b3c8bd9f4c..61b095e609aa0 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -17769,9 +17769,6 @@ "xpack.dataVisualizer.index.dataGrid.exploreInMapsTitle": "在 Maps 中浏览", "xpack.dataVisualizer.index.dataLoader.internalServerErrorMessage": "加载索引 {index} 中的数据时出错。{message}。请求可能已超时。请尝试使用较小的样例大小或缩小时间范围。", "xpack.dataVisualizer.index.dataViewErrorMessage": "查找数据视图时出错", - "xpack.dataVisualizer.index.dataViewManagement.actionsPopoverLabel": "数据视图设置", - "xpack.dataVisualizer.index.dataViewManagement.addFieldButton": "将字段添加到数据视图", - "xpack.dataVisualizer.index.dataViewManagement.manageFieldButton": "管理数据视图字段", "xpack.dataVisualizer.index.embeddableErrorDescription": "加载可嵌入对象时出现错误。请检查所有必需的输入是否有效。", "xpack.dataVisualizer.index.embeddableErrorTitle": "加载可嵌入对象时出错", "xpack.dataVisualizer.index.embeddableNoResultsMessage": "找不到结果", @@ -29188,15 +29185,10 @@ "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.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}。", @@ -29670,7 +29662,6 @@ "xpack.ml.customUrlsEditor.urlLabel": "URL", "xpack.ml.customUrlsList.invalidIntervalFormatErrorMessage": "时间间隔格式无效", "xpack.ml.dataDrift.createDataDriftDataViewTitle": "创建数据视图并分析数据偏移", - "xpack.ml.dataDrift.createDataViewButton": "创建数据视图", "xpack.ml.dataDrift.indexPatternsEditor.additionalSettingsTitle": "其他设置", "xpack.ml.dataDrift.indexPatternsEditor.allIndicesLabel": "{indicesLength, plural, one {# source} other {# sources} }", "xpack.ml.dataDrift.indexPatternsEditor.analyzeDataDriftLabel": "分析数据偏移", @@ -29692,7 +29683,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": "整个数据集的标准化混淆矩阵", @@ -30223,7 +30213,6 @@ "xpack.ml.dataViewUtils.dataViewTimeFieldLabel": "数据视图的时间字段", "xpack.ml.dataViewUtils.dataViewTitleError": "具有此名称的数据视图已存在。", "xpack.ml.dataViewUtils.noTimeFieldOptionLabel": "我不想使用时间字段选项", - "xpack.ml.datavisualizer": "(ES|QL)", "xpack.ml.dataVisualizer.dataDrift.docTitle": "数据偏移", "xpack.ml.dataVisualizer.dataDriftCustomIndexPatterns.docTitle": "数据偏移定制索引模式", "xpack.ml.dataVisualizer.dataView.docTitle": "索引数据可视化工具", @@ -30246,7 +30235,6 @@ "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "使用 Elastic 的管道查询语言分析 Elasticsearch 索引中的数据形状。", "xpack.ml.datavisualizer.selector.tryESQLNowButtonLabel": "打开 ES|QL 编辑器", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "上传数据文件。", - "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "使用 ES|QL", "xpack.ml.datavisualizer.startTrial.fullMLFeaturesDescription": "要体验{subscriptionsLink}提供的完整 Machine Learning 功能,请开始为期 30 天的试用。", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "白金级或企业级订阅", "xpack.ml.datavisualizerBreadcrumbLabel": "数据可视化工具", @@ -30602,7 +30590,6 @@ "xpack.ml.jobsBreadcrumbs.multiMetricLabel": "多指标", "xpack.ml.jobsBreadcrumbs.populationLabel": "填充", "xpack.ml.jobsBreadcrumbs.rareLabel": "极少", - "xpack.ml.jobsBreadcrumbs.selectDateViewLabel": "选择数据视图", "xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabelRecognize": "识别的索引", "xpack.ml.jobsBreadcrumbs.selectJobType": "创建作业", "xpack.ml.jobsBreadcrumbs.singleMetricLabel": "单一指标", @@ -31856,7 +31843,6 @@ "xpack.ml.ruleEditor.typicalAppliesTypeText": "典型", "xpack.ml.sampleDataLinkLabel": "ML 作业", "xpack.ml.savedObjectFinder.createADataView": "创建数据视图", - "xpack.ml.selectDataViewLabel": "选择数据视图", "xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "您有 {calendarsCountBadge} 个{calendarsDstCount, plural, one {calendar} other {日历}}", "xpack.ml.settings.anomalyDetection.calendarsDstText": "DST 日历包含您不希望为其生成异常的已计划事件列表,考虑可能导致事件提前或延后一小时发生的夏令时转换。", "xpack.ml.settings.anomalyDetection.calendarsDstTitle": "DST 日历", @@ -32507,7 +32493,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/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/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..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 @@ -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,21 @@ 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 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 +70,8 @@ export const ChangePointDetectionAppState: FC savedSearch, appContextValue, showFrozenDataTierChoice = true, + headerContent, + rightSideItems, }) => { const datePickerDeps: DatePickerDependencies = { ...pick(appContextValue, [ @@ -77,8 +87,6 @@ export const ChangePointDetectionAppState: FC showFrozenDataTierChoice, }; - const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); - const reload$ = useMemo>( () => merge( @@ -88,8 +96,22 @@ export const ChangePointDetectionAppState: FC [appContextValue.cps?.cpsManager] ); + if (!dataView) { + return null; + } + + const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); + if (warning !== null) { - return <>{warning}; + return ( + + + {headerContent} + + {warning} + + + ); } appContextValue.embeddingOrigin = AIOPS_EMBEDDABLE_ORIGIN.ML_AIOPS_LABS; @@ -101,10 +123,10 @@ 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..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 @@ -4,8 +4,9 @@ * 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 { EuiSpacer } from '@elastic/eui'; import { pick } from 'lodash'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; @@ -31,13 +32,19 @@ 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 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,13 +52,24 @@ export const LogCategorizationAppState: FC = ({ savedSearch, appContextValue, showFrozenDataTierChoice = true, + headerContent, }) => { - if (!dataView) return null; + if (!dataView) { + return null; + } const warning = timeSeriesDataViewWarning(dataView, 'log_categorization'); if (warning !== null) { - return <>{warning}; + return ( + + + {headerContent} + + {warning} + + + ); } const datePickerDeps: DatePickerDependencies = { @@ -76,10 +94,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..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 @@ -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,7 @@ import { AttachmentsMenu } from './attachments_menu'; const BAR_TARGET = 20; const DEFAULT_SELECTED_FIELD = 'message'; -export const LogCategorizationPage: FC = () => { +export const LogCategorizationPage: FC<{ headerContent?: ReactNode }> = ({ headerContent }) => { const { notifications: { toasts }, embeddingOrigin, @@ -349,7 +349,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..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 @@ -5,8 +5,9 @@ * 2.0. */ -import type { FC } from 'react'; +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'; @@ -34,7 +35,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 +44,12 @@ export interface LogRateAnalysisAppStateProps { showContextualInsights?: boolean; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; + /** + * 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,13 +58,24 @@ export const LogRateAnalysisAppState: FC = ({ appContextValue, showContextualInsights = false, showFrozenDataTierChoice = true, + headerContent, }) => { - if (!dataView) return null; + if (!dataView) { + return null; + } 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(); @@ -80,12 +98,15 @@ 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..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 @@ -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,12 @@ interface SignificantFieldValue { interface LogRateAnalysisPageProps { showContextualInsights?: boolean; + headerContent?: ReactNode; } export const LogRateAnalysisPage: FC = ({ showContextualInsights = false, + headerContent, }) => { const aiopsAppContext = useAiopsAppContext(); const { data: dataService, observabilityAIAssistant } = aiopsAppContext; @@ -296,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 b98d161d9a925..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 @@ -6,7 +6,7 @@ */ 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'; @@ -34,7 +34,17 @@ const maxInlineSizeStyles = css` min-inline-size: 0; `; -export const PageHeader: FC = () => { +export interface PageHeaderProps { + /** 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 = ({ rightSideItems, headerContent }) => { const [, setGlobalState] = useUrlState('_g'); const { dataView } = useDataSource(); @@ -72,9 +82,16 @@ export const PageHeader: FC = () => { return ( - -

{dataView.getName()}

-
+ {headerContent !== undefined ? ( + + {headerContent} + {rightSideItems ? {rightSideItems} : null} + + ) : ( + +

{dataView.getName()}

+
+ )}
{ const { services } = useMlKibana(); @@ -31,30 +34,45 @@ export const ChangePointDetectionPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const pageTitle = ( + + ); + + const headerContent = ( + + ); + return ( <> - + - - } - /> + - {dataView ? ( + {!dataView ? ( + <> + {headerContent} + + + ) : ( { fieldStats: { useFieldStatsTrigger, FieldStatsFlyoutProvider }, }} /> - ) : null} + )} { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const pageTitle = ( + + ); + + const headerContent = ( + + ); + return ( <> - - } - /> + - {dataView && ( + {!dataView ? ( + <> + {headerContent} + + + ) : ( { @@ -26,24 +30,36 @@ export const LogRateAnalysisPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const pageTitle = ( + + ); + + const headerContent = ( + + ); + return ( <> - - } - /> + - {dataView && ( + {!dataView ? ( + <> + {headerContent} + + + ) : ( ( + + + + } + body={ +

+ +

+ } + /> +); 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..375327c0668fb 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/app.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/app.tsx @@ -86,6 +86,7 @@ export const App: FC = ({ 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/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/contexts/kibana/kibana_context.ts b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts index af34723e9ac98..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 @@ -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'; @@ -46,6 +47,7 @@ interface StartPlugins { 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/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..5511514c77b2f --- /dev/null +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.test.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 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 mockGetDefaultId = jest.fn(); +const mockGetIdsWithTitle = jest.fn(); +const mockGetDataViewAndSavedSearch = jest.fn(); + +const buildKibanaMock = () => ({ + services: { + data: { + dataViews: { + get: mockGet, + getDefaultId: mockGetDefaultId, + getIdsWithTitle: mockGetIdsWithTitle, + }, + }, + savedSearch: mockGetDataViewAndSavedSearch, + uiSettings: {}, + }, +}); + +const renderProvider = (children =
Hello
) => + render( + + {children} + + ); + +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(''); + mockGetDefaultId.mockResolvedValue('default-dv'); + mockGet.mockResolvedValue({ id: 'default-dv', title: 'Default Data View' }); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGetDefaultId).toHaveBeenCalled(); + expect(mockGet).toHaveBeenCalledWith('default-dv'); + }); + + it('uses default data view when it has an id returned by getDefaultId', async () => { + mockLocationSearch.mockReturnValue(''); + mockGetDefaultId.mockResolvedValue('logs-star'); + mockGet.mockResolvedValue({ id: 'logs-star', title: 'logs-*' }); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + expect(mockGetDefaultId).toHaveBeenCalled(); + expect(mockGet).toHaveBeenCalledWith('logs-star'); + }); + + it('renders children with null data view when getDefaultId throws', async () => { + mockLocationSearch.mockReturnValue(''); + mockGetDefaultId.mockRejectedValue(new Error('No default data view')); + + renderProvider(); + + await waitFor(() => { + expect(screen.getByTestId('child-content')).toBeInTheDocument(); + }); + + 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(''); + mockGetDefaultId.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({ + 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; + mockGetDefaultId.mockReturnValue( + new Promise((resolve) => { + resolvePromise = resolve; + }) + ); + mockGet.mockResolvedValue({ id: 'default-dv', title: 'Default' }); + + const { container } = renderProvider(); + + expect(container.firstChild).toBeNull(); + + await waitFor(() => { + resolvePromise!('default-dv'); + }); + + 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/application/contexts/ml/data_source_context.tsx b/x-pack/platform/plugins/shared/ml/public/application/contexts/ml/data_source_context.tsx index 5855325f5918f..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 @@ -73,17 +73,35 @@ export const DataSourceContextProvider: FC> = ({ chil ); } - let dataViewAndSavedSearch: DataViewAndSavedSearch = { - savedSearch: null, - dataView: null, - }; + let dataViewAndSavedSearch: DataViewAndSavedSearch = { savedSearch: null, dataView: null }; + + const hasSavedSearchId = savedSearchId !== undefined; + const hasDataViewId = dataViewId !== undefined; + const shouldUseDefaultDataView = !hasSavedSearchId && !hasDataViewId; - if (savedSearchId !== undefined) { + if (hasSavedSearchId) { dataViewAndSavedSearch = await getDataViewAndSavedSearchCb(savedSearchId); - } else if (dataViewId !== undefined) { + } + + if (!hasSavedSearchId && hasDataViewId) { dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId); } + 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( 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..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 @@ -7,17 +7,21 @@ 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 { MlDataSourcePicker } from '@kbn/aiops-components'; +import { DataViewPicker } from '@kbn/unified-search-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useMlKibana } from '../../contexts/kibana'; import { useDataSource } from '../../contexts/ml'; import { MlPageHeader } from '../../components/page_header'; import { PageTitle } from '../../components/page_title'; export const DataDriftPage: FC = () => { - const { - services: { dataVisualizer }, - } = useMlKibana(); + const { services } = useMlKibana(); + const { dataVisualizer } = services; const [DataDriftView, setDataDriftView] = useState(null); @@ -30,21 +34,54 @@ export const DataDriftPage: FC = () => { const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); + const dataSourcePicker = ( + + ); + return ( <> + } /> {dataView && DataDriftView ? ( - - ) : null} + + ) : dataView ? 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/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 4a9f1ca7a92fc..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 @@ -10,8 +10,8 @@ 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 { useTimefilter } from '@kbn/ml-date-picker'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; +import { EuiEmptyPrompt } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import type { GetAdditionalLinksParams, @@ -19,6 +19,9 @@ import type { GetAdditionalLinks, } from '@kbn/file-upload-common'; 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 } from '../../contexts/kibana'; import { HelpMenu } from '../../components/help_menu'; import { isFullLicense } from '../../license'; @@ -26,28 +29,33 @@ 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'; + 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 mlApi = useMlApi(); const { showNodeInfo } = useEnabledFeatures(); + const { selectedDataView: dataView } = useDataSource(); const mlLocator = useMlLocator()!; const mlManagementLocator = useMlManagementLocator(); const mlFeaturesDisabled = !isFullLicense(); - getMlNodeCount(mlApi); + + useEffect(() => { + getMlNodeCount(mlApi); + }, [mlApi]); const [IndexDataVisualizer, setIndexDataVisualizer] = useState( null @@ -190,34 +198,67 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) [mlLocator, mlFeaturesDisabled] ); + const dataSourcePicker = !esql ? ( + mlTimefilterRefresh$.next({ lastRefresh: Date.now() })} + /> + ) : undefined; + return IndexDataVisualizer ? ( {IndexDataVisualizer !== null ? ( <> - - + ) : ( + ) + } + /> + + {!dataView && !esql ? ( + <> + {dataSourcePicker} + + + + } + body={ +

+ +

} /> - {esql ? ( - <> - - - - - ) : null} -
- - + + ) : ( + + )} ) : null} 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/overview/data_visualizer_grid.tsx b/x-pack/platform/plugins/shared/ml/public/application/overview/data_visualizer_grid.tsx index 16e20b9d67a07..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 { 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" > @@ -304,9 +304,7 @@ export const OverviewPage: FC = () => { - navigateToPath('/aiops/change_point_detection_index_select') - } + onClick={() => navigateToPath('/aiops/change_point_detection')} data-test-subj="mlOverviewCardChangePointDetectionButton" aria-label={i18n.translate( 'xpack.ml.overview.changePointDetection.findChangesButton', 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..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,96 +120,11 @@ export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ deepLinkId: 'ml:dataVisualizer', }); -// we need multiple AIOPS_BREADCRUMB breadcrumb items as they each need to link -// to each of the AIOps pages. -export const AIOPS_BREADCRUMB_LOG_RATE_ANALYSIS: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { - defaultMessage: 'AIOps Labs', - }), - href: '/aiops/log_rate_analysis_index_select', -}); - -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', -}); - -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', -}); - -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', - 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_index_select', - 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_index_select', - 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', - }), - href: '/data_drift_index_select', - 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', -}); - -export const DATA_DRIFT_INDEX_SELECT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ - text: i18n.translate('xpack.ml.settings.breadcrumbs.dataComparisonLabel', { - defaultMessage: 'Select data view', - }), - href: '/data_drift_index_select', deepLinkId: 'ml:dataDrift', }); @@ -228,18 +143,7 @@ type ManagementBreadcrumb = keyof typeof managementBreadcrumbs; const breadcrumbs = { ML_BREADCRUMB, - DATA_DRIFT_INDEX_SELECT_BREADCRUMB, - DATA_DRIFT_PAGE, DATA_VISUALIZER_BREADCRUMB, - AIOPS_BREADCRUMB_LOG_RATE_ANALYSIS, - AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS, - AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION, - 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; diff --git a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/change_point_detection.tsx b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/change_point_detection.tsx index 61119ce1ca798..f49dcd847a5d5 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/change_point_detection.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/aiops/change_point_detection.tsx @@ -34,7 +34,6 @@ export const changePointDetectionRouteFactory = ( render: () => , 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/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/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/anomaly_detection_management.ts b/x-pack/platform/plugins/shared/ml/public/application/routing/routes/anomaly_detection_management.ts index 586d9da08dccb..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 @@ -6,6 +6,9 @@ */ 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 * from './jobs_list'; export * from './supplied_configurations'; 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 c881a01578c81..0000000000000 --- a/x-pack/platform/plugins/shared/ml/public/application/routing/routes/data_view_select.tsx +++ /dev/null @@ -1,61 +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 { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -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 => ({ - 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), -}); 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..d45503daf353e 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 @@ -9,58 +9,16 @@ import { i18n } from '@kbn/i18n'; import type { FC } from 'react'; import React from 'react'; 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 => ({ - id: 'dataDrift', - path: createPath(ML_PAGES.DATA_DRIFT_INDEX_SELECT), - title: i18n.translate('xpack.ml.dataVisualizer.dataDrift.docTitle', { - defaultMessage: 'Data Drift', - }), - render: (props, deps) => ( - - ), - 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', - }), - }, - ], - 'data-test-subj': 'mlPageDataDrift', -}); - export const dataDriftRouteIndexPatternFactory = ( navigateToPath: NavigateToPath, basePath: string @@ -70,21 +28,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 +41,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/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 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/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..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 @@ -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, @@ -216,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 () => { 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/plugin.ts b/x-pack/platform/plugins/shared/ml/public/plugin.ts index a879aa0653303..230607d5620c4 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'; @@ -93,6 +94,7 @@ export interface MlStartDependencies { dashboard: DashboardStart; data: DataPublicPluginStart; dataViewEditor: DataViewEditorStart; + dataViewFieldEditor: DataViewFieldEditorStart; dataVisualizer: DataVisualizerPluginStart; embeddable: EmbeddableStart; fieldFormats: FieldFormatsRegistry; @@ -206,6 +208,7 @@ export class MlPlugin implements Plugin { 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/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..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 @@ -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', @@ -223,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: [], }; }, @@ -254,7 +243,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 => { diff --git a/x-pack/platform/plugins/shared/ml/tsconfig.json b/x-pack/platform/plugins/shared/ml/tsconfig.json index 0ce3eb6bb57e7..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", @@ -43,6 +44,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", 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 685eeb12f20bc..dda1c987cf46d 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 @@ -31,7 +31,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(); }); @@ -225,7 +226,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 43aec4dacec7d..c6dee80ade0d3 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 @@ -59,7 +59,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`); @@ -100,7 +101,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 7d726fbc9ac15..ec2af98313a1b 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 @@ -11,15 +11,21 @@ import { farequoteDataViewTestData, farequoteLuceneSearchTestData } from '../ind export default function ({ getPageObject, getService }: FtrProviderContext) { 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']); @@ -44,7 +50,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( @@ -57,7 +64,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 3973b567faa8d..86ef463fba3d5 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 @@ -102,6 +102,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'), ]); }); @@ -126,7 +127,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); @@ -234,5 +236,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/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 78aed067c7d06..826c92f91a399 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 @@ -85,7 +85,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 b2562dd793630..f45717f75552b 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 @@ -27,6 +27,7 @@ interface TestData { export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); + const testSubjects = getService('testSubjects'); const originalTestData: TestData = { suiteTitle: 'original data view', @@ -198,6 +199,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 @@ -243,6 +249,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..33f5601e0ccb8 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`); }); }, @@ -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/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'); }, }; } 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 () => { 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 c1a51640d142b..f2afeef3b2075 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 fa67f5793f2d6..c42ddd1e35437 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 009ef9735917c..b707cbf5af9c5 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 84bab533770d9..86549f0420939 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', - }, ], }, {