Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
42b721c
refactor(ml): remove intermediate step with picking data source for D…
KodeRad May 11, 2026
b964868
feat(ml): enhance DataSourcePicker to require time-based data views
KodeRad May 11, 2026
b657957
fix(ml): update navigation paths for AIOps buttons in OverviewPage
KodeRad May 11, 2026
7710de0
feat(ml): add header content support to data visualizer components
KodeRad May 11, 2026
0071085
refactor(ml): reuse DataViewPicker
KodeRad May 14, 2026
628d5c6
fix(ml): add pageTitle prop to AIOps components
KodeRad May 15, 2026
bc1ef26
refactor(ml): move getMlNodeCount call into useEffect for data visual…
KodeRad May 15, 2026
75113ab
refactor(ml): add OpenSessionPanel mock and simplify MlDataSourcePick…
KodeRad May 15, 2026
523e761
refactor(ml): replace OpenSessionPanel with ML specific implementation
KodeRad May 15, 2026
c5c9fed
refactor(ml): move MlDataSourcePicker and MlOpenSessionFlyout components
KodeRad May 15, 2026
e45e78b
refactor(ml): remove indexDataVisualizerPage references and clean up …
KodeRad May 15, 2026
bd83912
test(ml): add unit tests for MlDataSourcePicker and DataSourceContext…
KodeRad May 15, 2026
8ad9467
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine May 19, 2026
d58adfe
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine May 19, 2026
636c1fb
refactor(ml): update i18n keys for MlDataSourcePicker and MlOpenSessi…
KodeRad May 19, 2026
30fc683
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine May 19, 2026
14e710e
feat(ml): add filterEsql prop to MlDataSourcePicker and MlOpenSession…
KodeRad May 19, 2026
1687392
fix(ml): proper breadcrumbs and disableSearchSession in management co…
KodeRad May 20, 2026
bd650e0
refactor(ml): optimize MlDataSourcePicker and IndexDataVisualizerPage…
KodeRad May 20, 2026
f6fa32b
refactor(ml): enhance data view selection and update test cases
KodeRad May 21, 2026
8416653
refactor(ml): unify two modes of Index Data Visualizer page into one
KodeRad May 22, 2026
f2f0d7a
test(ml): enhance DataSourceContextProvider tests for default data vi…
KodeRad May 22, 2026
5930d30
refactor(ml): remove DATA_DRIFT_INDEX_SELECT references and update ro…
KodeRad May 24, 2026
60b8158
refactor(ml): clean up unused constants and breadcrumbs
KodeRad May 25, 2026
1845e03
refactor(ml): remove pageTitle prop from various components and intro…
KodeRad May 25, 2026
e6090fe
refactor(ml): remove headerContent handling from app state components
KodeRad May 25, 2026
bda4791
refactor(ml): improve default data view handling in DataSourceContext…
KodeRad May 25, 2026
e11dede
refactor(ml): revert ComboBoxService option retrieval
KodeRad May 26, 2026
ba0ff48
refactor(ml): remove filterEsql prop from MlDataSourcePicker and MlOp…
KodeRad May 26, 2026
13f3d46
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine May 26, 2026
b7c66f9
refactor(ml): enhance data view retrieval logic in DataSourceContextP…
KodeRad May 26, 2026
1ba583e
refactor(ml): unify no data view messages i18n ids
KodeRad May 30, 2026
10e51b1
Merge branch 'main' into ml-data-view-selector-heading
KodeRad Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/platform/packages/shared/deeplinks/ml/deep_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export type LinkId =
| 'dataVisualizer'
| 'fileUpload'
| 'indexDataVisualizer'
| 'indexDataVisualizerPage'
| 'settings'
| 'calendarSettings'
| 'calendarSettings'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function ChangeDataView({
onCreateDefaultAdHocDataView,
onClosePopover,
getDataViewHelpText,
compressed = true,
}: DataViewPickerProps) {
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
Expand Down Expand Up @@ -126,7 +127,7 @@ export function ChangeDataView({
const { label, title, 'data-test-subj': dataTestSubj, fullWidth, ...rest } = trigger;
return (
<EuiFormControlButton
compressed
compressed={compressed}
css={styles.trigger}
isInvalid={isMissingCurrent}
title={trigger.label}
Expand Down Expand Up @@ -328,7 +329,7 @@ export function ChangeDataView({
<>
<EuiFlexItem grow={true} css={shrinkableContainerCss}>
<EuiFormControlLayout
compressed
compressed={compressed}
isDropdown
prepend={i18n.translate('unifiedSearch.query.queryBar.esqlMenu.switcherLabelTitle', {
defaultMessage: 'Data view',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export interface DataViewPickerProps {
* The properties of the button that triggers the dataview picker.
*/
trigger: ChangeDataViewTriggerProps;
/**
* When false, renders the trigger at standard (non-compressed) height.
* Defaults to true to preserve the default compact toolbar appearance.
*/
compressed?: boolean;
/**
* Flag that should be enabled when the current dataview is missing.
*/
Expand Down Expand Up @@ -95,6 +100,7 @@ export const DataViewPicker = ({
onCreateDefaultAdHocDataView,
isDisabled,
getDataViewHelpText,
compressed = true,
}: DataViewPickerProps) => {
return (
<ChangeDataView
Expand All @@ -112,6 +118,7 @@ export const DataViewPicker = ({
selectableProps={selectableProps}
isDisabled={isDisabled}
getDataViewHelpText={getDataViewHelpText}
compressed={compressed}
/>
);
};
17 changes: 13 additions & 4 deletions x-pack/performance/journeys_e2e/tsdb_logs_data_visualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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 });
Expand Down
10 changes: 10 additions & 0 deletions x-pack/platform/packages/private/ml/aiops_components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
7 changes: 7 additions & 0 deletions x-pack/platform/packages/private/ml/aiops_components/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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<string, any> = {};
const MockDataViewPicker = (props: any) => {
capturedDataViewPickerProps = props;
return (
<div data-test-subj="mockDataViewPicker">
<span>{props.trigger?.label}</span>
</div>
);
};

jest.mock('./ml_open_session_flyout', () => ({
MlOpenSessionFlyout: (props: any) => {
return (
<div data-test-subj="mockOpenSessionFlyout">
<button onClick={props.onClose} data-test-subj="closeSessionPanel">
Close
</button>
<button
onClick={() => props.onOpenSavedSearch('saved-search-id-1')}
data-test-subj="openSavedSearch"
>
Open Saved Search
</button>
</div>
);
},
}));

const mockGetIdsWithTitle = jest.fn().mockResolvedValue([]);
const mockOpenEditor = jest.fn().mockResolvedValue(() => {});

const buildServices = (
overrides?: Partial<MlDataSourcePickerServices>
): 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 = () => <div data-test-subj="mockSavedObjectFinder" />;

const renderComponent = (props: { currentDataView: any; services?: MlDataSourcePickerServices }) =>
render(
<IntlProvider locale="en">
<MlDataSourcePicker
currentDataView={props.currentDataView}
services={props.services ?? buildServices()}
DataViewPickerComponent={MockDataViewPicker}
SavedObjectFinderComponent={MockSavedObjectFinder}
/>
</IntlProvider>
);

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=<id> 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=<id> 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();
});
});
Loading
Loading