Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0756847
[Logs] Replace log stream component (#219425)
achyutjhunjhunwala Jun 19, 2025
5c2f0c7
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 19, 2025
32e2e2c
[CI] Auto-commit changed files from 'node scripts/styled_components_m…
kibanamachine Jun 19, 2025
db84073
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Jun 19, 2025
4395680
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 19, 2025
e895ab4
Not sure why these did not backport
achyutjhunjhunwala Jun 19, 2025
5f6796c
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 19, 2025
48e9c81
Fix wierd 8.19 issues
achyutjhunjhunwala Jun 19, 2025
d6be0f8
Fix deleted text by mistake
achyutjhunjhunwala Jun 20, 2025
bb46bbc
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 20, 2025
b858e99
Fix more check type issues with backporting
achyutjhunjhunwala Jun 20, 2025
e866658
Revert last change as we don't want to break users bookmarked stream url
achyutjhunjhunwala Jun 20, 2025
528cbe9
Fix agent log unit tests which failed as mock needs updae
achyutjhunjhunwala Jun 20, 2025
d77f170
Fix infra ftr tests for pages no more available like settings and stream
achyutjhunjhunwala Jun 20, 2025
a0eb8a1
Revert mess up by deleting setting page and title of left link from D…
achyutjhunjhunwala Jun 20, 2025
d327fc5
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 20, 2025
6861328
Fix checktype issues
achyutjhunjhunwala Jun 20, 2025
3e54765
Fix log stream tests
achyutjhunjhunwala Jun 21, 2025
e37b1fa
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 21, 2025
9cd16b6
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Jun 21, 2025
4ae8fbb
Fix checktype issues on Log Sources
achyutjhunjhunwala Jun 21, 2025
f686709
Remove Log Stream Date Nano tests as Log Stream is no more
achyutjhunjhunwala Jun 21, 2025
9e3713c
Merge branch '8.19' into backport/8.19/pr-219425
achyutjhunjhunwala Jun 21, 2025
fa20247
Remove Log Stream Date as Log Stream is no more
achyutjhunjhunwala Jun 22, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const DiscoverGrid: React.FC<DiscoverGridProps> = ({
rowAdditionalLeadingControls: customRowAdditionalLeadingControls,
...props
}) => {
const { dataView, setExpandedDoc } = props;
const { dataView, setExpandedDoc, renderDocumentView } = props;
const getRowIndicatorProvider = useProfileAccessor('getRowIndicatorProvider');
const getRowIndicator = useMemo(() => {
return getRowIndicatorProvider(() => undefined)({ dataView: props.dataView });
Expand All @@ -48,6 +48,7 @@ export const DiscoverGrid: React.FC<DiscoverGridProps> = ({
query,
updateESQLQuery: onUpdateESQLQuery,
setExpandedDoc,
isDocViewerEnabled: !!renderDocumentView,
});
}, [
customRowAdditionalLeadingControls,
Expand All @@ -56,6 +57,7 @@ export const DiscoverGrid: React.FC<DiscoverGridProps> = ({
onUpdateESQLQuery,
query,
setExpandedDoc,
renderDocumentView,
]);

const getPaginationConfigAccessor = useProfileAccessor('getPaginationConfig');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const getRowAdditionalLeadingControls: LogsDataSourceProfileProvider['pro
(prev, { context }) =>
(params) => {
const additionalControls = prev(params) || [];
const { updateESQLQuery, query, setExpandedDoc } = params;
const { updateESQLQuery, query, setExpandedDoc, isDocViewerEnabled } = params;

const isDegradedDocsControlEnabled = isOfAggregateQueryType(query)
? queryContainsMetadataIgnored(query)
Expand Down Expand Up @@ -52,15 +52,17 @@ export const getRowAdditionalLeadingControls: LogsDataSourceProfileProvider['pro
setExpandedDoc(props.record, { initialTabId: 'doc_view_logs_overview' });
};

return [
...additionalControls,
createDegradedDocsControl({
enabled: isDegradedDocsControlEnabled,
addIgnoredMetadataToQuery,
onClick: leadingControlClick('quality_issues'),
}),
createStacktraceControl({ onClick: leadingControlClick('stacktrace') }),
];
return isDocViewerEnabled
? [
...additionalControls,
createDegradedDocsControl({
enabled: isDegradedDocsControlEnabled,
addIgnoredMetadataToQuery,
onClick: leadingControlClick('quality_issues'),
}),
createStacktraceControl({ onClick: leadingControlClick('stacktrace') }),
]
: additionalControls;
};

const queryContainsMetadataIgnored = (query: AggregateQuery) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,29 @@ describe('logsDataSourceProfileProvider', () => {
});
const rowAdditionalLeadingControls = getRowAdditionalLeadingControls?.({
dataView: dataViewWithLogLevel,
isDocViewerEnabled: true,
});

expect(rowAdditionalLeadingControls).toHaveLength(2);
expect(rowAdditionalLeadingControls?.[0].id).toBe('connectedDegradedDocs');
expect(rowAdditionalLeadingControls?.[1].id).toBe('connectedStacktraceDocs');
});

it('should not return the passed additional controls if the flag is turned off', () => {
const getRowAdditionalLeadingControls =
logsDataSourceProfileProvider.profile.getRowAdditionalLeadingControls?.(() => undefined, {
context: {
category: DataSourceCategory.Logs,
logOverviewContext$: new BehaviorSubject<LogOverviewContext | undefined>(undefined),
},
});
const rowAdditionalLeadingControls = getRowAdditionalLeadingControls?.({
dataView: dataViewWithLogLevel,
isDocViewerEnabled: false,
});

expect(rowAdditionalLeadingControls).toHaveLength(0);
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ export interface RowControlsExtensionParams {
* @param options.initialTabId - The tabId to display in the flyout
*/
setExpandedDoc?: (record?: DataTableRecord, options?: { initialTabId?: string }) => void;
/**
* Flag to indicate if Flyout opening controls must be rendered or not
*/
isDocViewerEnabled: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export function SearchEmbeddableGridComponent({

// `api.query$` and `api.filters$` are the initial values from the saved search SO (as of now)
// `fetchContext.query` and `fetchContext.filters` are Dashboard's query and filters

const savedSearchQuery = apiQuery;
const savedSearchFilters = apiFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER: Trigger = {
'This trigger is used to replace the cell actions for Discover session embeddable grid.',
} as const;

export const LEGACY_LOG_STREAM_EMBEDDABLE = 'LOG_STREAM_EMBEDDABLE';

export const ACTION_VIEW_SAVED_SEARCH = 'ACTION_VIEW_SAVED_SEARCH';

export const DEFAULT_HEADER_ROW_HEIGHT_LINES = 3;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { getAllLogsDataViewSpec } from '@kbn/discover-utils/src';
import { toSavedSearchAttributes } from '@kbn/saved-search-plugin/common';
import { getSearchEmbeddableFactory } from './get_search_embeddable_factory';
import { LEGACY_LOG_STREAM_EMBEDDABLE } from './constants';

export const getLegacyLogStreamEmbeddableFactory = (
...[{ startServices, discoverServices }]: Parameters<typeof getSearchEmbeddableFactory>
) => {
const searchEmbeddableFactory = getSearchEmbeddableFactory({ startServices, discoverServices });
const logStreamEmbeddableFactory: ReturnType<typeof getSearchEmbeddableFactory> = {
type: LEGACY_LOG_STREAM_EMBEDDABLE,
buildEmbeddable: async ({ initialState, ...restParams }) => {
const searchSource = await discoverServices.data.search.searchSource.create();
let fallbackPattern = 'logs-*-*';
// Given that the logDataAccess service is an optional dependency with discover, we need to check if it exists
if (discoverServices.logsDataAccess) {
fallbackPattern =
await discoverServices.logsDataAccess.services.logSourcesService.getFlattenedLogSources();
}

const spec = getAllLogsDataViewSpec({ allLogsIndexPattern: fallbackPattern });
const dataView: DataView = await discoverServices.data.dataViews.create(spec);

// Finally assign the data view to the search source
searchSource.setField('index', dataView);

const savedSearch: SavedSearch = {
title: initialState.rawState.title,
description: initialState.rawState.description,
timeRange: initialState.rawState.timeRange,
sort: initialState.rawState.sort ?? [],
columns: initialState.rawState.columns ?? [],
searchSource,
managed: false,
};
const { searchSourceJSON, references } = searchSource.serialize();

initialState = {
...initialState,
rawState: {
...initialState.rawState,
attributes: {
...toSavedSearchAttributes(savedSearch, searchSourceJSON),
references,
},
},
};

return searchEmbeddableFactory.buildEmbeddable({ initialState, ...restParams });
},
};

return logStreamEmbeddableFactory;
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,85 +34,112 @@ describe('initialize edit api', () => {
const waitOneTick = () => new Promise((resolve) => setTimeout(resolve, 0));

describe('get app target', () => {
const runEditLinkTest = async (dataView?: DataView, byValue?: boolean) => {
jest
.spyOn(discoverServiceMock.locator, 'getUrl')
.mockClear()
.mockResolvedValueOnce('/base/mock-url');
jest
.spyOn(discoverServiceMock.core.http.basePath, 'remove')
.mockClear()
.mockReturnValueOnce('/mock-url');

if (dataView) {
mockedApi.dataViews$.next([dataView]);
const runEditLinkTest = async (dataViewInput?: DataView, byValue?: boolean) => {
const currentDataView = dataViewInput || dataViewMock;
// Determine if the current scenario will use a redirect
const currentSavedObjectId = byValue ? undefined : 'test-id';
const isDataViewPersisted = currentDataView.isPersisted
? currentDataView.isPersisted()
: true; // Assume persisted if method undefined
const useRedirect = !currentSavedObjectId && !isDataViewPersisted;

if (useRedirect) {
// This is the "by value with ad hoc data view" (redirect) case.
jest
.spyOn(discoverServiceMock.locator, 'getUrl')
.mockClear()
.mockResolvedValueOnce('/base/state-url-for-redirect'); // For urlWithoutLocationState
jest
.spyOn(discoverServiceMock.core.http.basePath, 'remove')
.mockClear()
.mockReturnValueOnce('/mock-url'); // For editPath (applied to getRedirectUrl result)
} else {
mockedApi.dataViews$.next([dataViewMock]);
}
if (byValue) {
mockedApi.savedObjectId$.next(undefined);
} else {
mockedApi.savedObjectId$.next('test-id');
// This is a "by reference" or "by value with persisted data view" (non-redirect) case.
jest
.spyOn(discoverServiceMock.locator, 'getUrl')
.mockClear()
.mockResolvedValueOnce('/base/discover-home') // For getUrl({}) -> urlWithoutLocationState
.mockResolvedValueOnce('/base/mock-url'); // For getUrl(locatorParams) -> raw editUrl
jest
.spyOn(discoverServiceMock.core.http.basePath, 'remove')
.mockClear()
.mockReturnValueOnce('/mock-url'); // For remove('/base/mock-url') -> editPath
}

mockedApi.dataViews$.next([currentDataView]);
mockedApi.savedObjectId$.next(currentSavedObjectId);

await waitOneTick();

const {
path: editPath,
app: editApp,
editUrl,
urlWithoutLocationState,
} = await getAppTarget(mockedApi, discoverServiceMock);

return { editPath, editApp, editUrl };
return { editPath, editApp, editUrl, urlWithoutLocationState };
};

const testByReference = ({
const testByReferenceOrNonRedirectValue = ({
editPath,
editApp,
editUrl,
urlWithoutLocationState,
}: {
editPath: string;
editApp: string;
editUrl: string;
urlWithoutLocationState: string;
}) => {
const locatorParams = getDiscoverLocatorParams(mockedApi);
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledTimes(1);
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledWith(locatorParams);
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledTimes(2);
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledWith({}); // For urlWithoutLocationState
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledWith(locatorParams); // For raw editUrl

expect(discoverServiceMock.core.http.basePath.remove).toHaveBeenCalledTimes(1);
expect(discoverServiceMock.core.http.basePath.remove).toHaveBeenCalledWith('/base/mock-url');

expect(editApp).toBe('discover');
expect(editPath).toBe('/mock-url');
expect(editUrl).toBe('/base/mock-url');
expect(editPath).toBe('/mock-url'); // Result of basePath.remove
expect(editUrl).toBe('/base/mock-url'); // Raw editUrl before basePath.remove
expect(urlWithoutLocationState).toBe('/base/discover-home');
};

it('should correctly output edit link params for by reference saved search', async () => {
const { editPath, editApp, editUrl } = await runEditLinkTest();
testByReference({ editPath, editApp, editUrl });
const result = await runEditLinkTest(dataViewMock, false);
testByReferenceOrNonRedirectValue(result);
});

it('should correctly output edit link params for by reference saved search with ad hoc data view', async () => {
const { editPath, editApp, editUrl } = await runEditLinkTest(dataViewAdHoc);
testByReference({ editPath, editApp, editUrl });
// Still "by reference" (savedObjectId exists), so no redirect even with ad-hoc data view.
const result = await runEditLinkTest(dataViewAdHoc, false);
testByReferenceOrNonRedirectValue(result);
});

it('should correctly output edit link params for by value saved search', async () => {
const { editPath, editApp, editUrl } = await runEditLinkTest(undefined, true);
testByReference({ editPath, editApp, editUrl });
it('should correctly output edit link params for by value saved search (with persisted data view)', async () => {
// "by value" but with a persisted data view (dataViewMock), so no redirect.
const result = await runEditLinkTest(dataViewMock, true);
testByReferenceOrNonRedirectValue(result);
});

it('should correctly output edit link params for by value saved search with ad hoc data view', async () => {
// This specific test case mocks getRedirectUrl because it's unique to the redirect flow
jest
.spyOn(discoverServiceMock.locator, 'getRedirectUrl')
.mockClear()
.mockReturnValueOnce('/base/mock-url');
jest
.spyOn(discoverServiceMock.core.http.basePath, 'remove')
.mockClear()
.mockReturnValueOnce('/mock-url');
.mockReturnValueOnce('/base/mock-url'); // This will be the raw editUrl

const { editPath, editApp, editUrl } = await runEditLinkTest(dataViewAdHoc, true);
const result = await runEditLinkTest(dataViewAdHoc, true);
const { editPath, editApp, editUrl, urlWithoutLocationState } = result;

const locatorParams = getDiscoverLocatorParams(mockedApi);

// Assertions for urlWithoutLocationState part (getUrl({}))
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledTimes(1);
expect(discoverServiceMock.locator.getUrl).toHaveBeenCalledWith({});

// Assertions for redirect part (getRedirectUrl and basePath.remove)
expect(discoverServiceMock.locator.getRedirectUrl).toHaveBeenCalledTimes(1);
expect(discoverServiceMock.locator.getRedirectUrl).toHaveBeenCalledWith(locatorParams);
expect(discoverServiceMock.core.http.basePath.remove).toHaveBeenCalledTimes(1);
Expand All @@ -121,6 +148,7 @@ describe('initialize edit api', () => {
expect(editApp).toBe('r');
expect(editPath).toBe('/mock-url');
expect(editUrl).toBe('/base/mock-url');
expect(urlWithoutLocationState).toBe('/base/state-url-for-redirect');
});
});

Expand All @@ -130,13 +158,26 @@ describe('initialize edit api', () => {
navigateToEditor: mockedNavigate,
}));
mockedApi.dataViews$.next([dataViewMock]);
mockedApi.savedObjectId$.next('test-id'); // Assuming a by-reference scenario for onEdit
await waitOneTick();

// Mocking for getAppTarget call within onEdit
// Assuming a non-redirect case for simplicity
jest
.spyOn(discoverServiceMock.locator, 'getUrl')
.mockClear()
.mockResolvedValueOnce('/base/discover-home-for-onedit') // For getUrl({})
.mockResolvedValueOnce('/base/mock-url-for-onedit'); // For getUrl(locatorParams)
jest
.spyOn(discoverServiceMock.core.http.basePath, 'remove')
.mockClear()
.mockReturnValueOnce('/mock-url-for-onedit');

const { onEdit } = initializeEditApi({
uuid: 'test',
parentApi: {
getAppContext: jest.fn().mockResolvedValue({
getCurrentPath: jest.fn(),
getAppContext: jest.fn().mockReturnValue({
getCurrentPath: jest.fn().mockReturnValue('/current-parent-path'),
currentAppId: 'dashboard',
}),
},
Expand All @@ -148,8 +189,12 @@ describe('initialize edit api', () => {
await onEdit();
expect(mockedNavigate).toBeCalledTimes(1);
expect(mockedNavigate).toBeCalledWith('discover', {
path: '/mock-url',
state: expect.any(Object),
path: '/mock-url-for-onedit',
state: expect.objectContaining({
embeddableId: 'test',
originatingApp: 'dashboard',
originatingPath: '/current-parent-path',
}),
});
});
});
Loading
Loading