Revert "[Unified search] Display the solutions recommended queries in the help menu" (#223598)

Reverts elastic/kibana#223362

I accidentally merged the previous PR, I am so sorry. I am reverting and
open it again to get the presentation team approval
This commit is contained in:
Stratoula Kalafateli 2025-06-12 18:19:11 +02:00 committed by GitHub
parent 25fe1fa0e2
commit a29fee8922
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 27 additions and 217 deletions

View file

@ -34,5 +34,3 @@ export {
type InferenceEndpointsAutocompleteResult,
type InferenceEndpointAutocompleteItem,
} from './src/inference_endpoint_autocomplete_types';
export { REGISTRY_EXTENSIONS_ROUTE } from './src/constants';

View file

@ -1,10 +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", 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".
*/
export const REGISTRY_EXTENSIONS_ROUTE = '/internal/esql_registry/extensions/';

View file

@ -63,6 +63,4 @@ export {
export { getRecommendedQueries } from './src/autocomplete/recommended_queries/templates';
export { getRecommendedQueriesTemplatesFromExtensions } from './src/autocomplete/recommended_queries/suggestions';
export { esqlFunctionNames } from './src/definitions/generated/function_names';

View file

@ -16,7 +16,7 @@ import type { IndexManagementPluginSetup } from '@kbn/index-management-shared-ty
import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import { type IndicesAutocompleteResult, REGISTRY_EXTENSIONS_ROUTE } from '@kbn/esql-types';
import type { IndicesAutocompleteResult } from '@kbn/esql-types';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import type { KibanaProject as SolutionId } from '@kbn/projects-solutions-groups';
@ -137,7 +137,7 @@ export class EsqlPlugin implements Plugin<{}, EsqlPluginStart> {
activeSolutionId: SolutionId
) => {
const result = await core.http.get(
`${REGISTRY_EXTENSIONS_ROUTE}${activeSolutionId}/${queryString}`
`/internal/esql_registry/extensions/${activeSolutionId}/${queryString}`
);
return result;
};

View file

@ -39,24 +39,6 @@ export class EsqlServerPlugin implements Plugin<EsqlServerPluginSetup> {
}),
});
this.extensionsRegistry.setRecommendedQueries(
[
{
name: 'Logs count by log level',
query: 'from logs* | STATS count(*) by log_level',
},
{
name: 'Apache logs counts',
query: 'from logs-apache_error | STATS count(*)',
},
{
name: 'Another index, not logs',
query: 'from movies | STATS count(*)',
},
],
'oblt'
);
registerRoutes(core, this.extensionsRegistry, initContext);
return {

View file

@ -12,7 +12,7 @@ import {
KIBANA_PROJECTS as VALID_SOLUTION_IDS,
} from '@kbn/projects-solutions-groups';
import type { IRouter, PluginInitializerContext } from '@kbn/core/server';
import { type ResolveIndexResponse, REGISTRY_EXTENSIONS_ROUTE } from '@kbn/esql-types';
import type { ResolveIndexResponse } from '@kbn/esql-types';
import type { ESQLExtensionsRegistry } from '../extensions_registry';
/**
@ -39,7 +39,7 @@ export const registerESQLExtensionsRoute = (
) => {
router.get(
{
path: `${REGISTRY_EXTENSIONS_ROUTE}{solutionId}/{query}`,
path: '/internal/esql_registry/extensions/{solutionId}/{query}',
security: {
authz: {
enabled: false,

View file

@ -8,8 +8,7 @@
*/
import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { screen, render, waitFor } from '@testing-library/react';
import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { stubIndexPattern } from '@kbn/data-plugin/public/stubs';
@ -17,22 +16,12 @@ import { coreMock } from '@kbn/core/public/mocks';
import type { DataView } from '@kbn/data-views-plugin/common';
import { ESQLMenuPopover } from './esql_menu_popover';
const startMock = coreMock.createStart();
// Mock the necessary services
startMock.chrome.getActiveSolutionNavId$.mockReturnValue(new BehaviorSubject('oblt'));
const httpModule = {
http: {
get: jest.fn().mockResolvedValue({ recommendedQueries: [] }), // Mock the HTTP GET request
},
};
const services = {
docLinks: startMock.docLinks,
http: httpModule.http,
chrome: startMock.chrome,
};
describe('ESQLMenuPopover', () => {
const renderESQLPopover = (adHocDataview?: DataView) => {
const startMock = coreMock.createStart();
const services = {
docLinks: startMock.docLinks,
};
return render(
<KibanaContextProvider services={services}>
<ESQLMenuPopover adHocDataview={adHocDataview} />
@ -40,11 +29,6 @@ describe('ESQLMenuPopover', () => {
);
};
beforeEach(() => {
// Reset mocks before each test
httpModule.http.get.mockClear();
});
it('should render a button', () => {
renderESQLPopover();
expect(screen.getByTestId('esql-menu-button')).toBeInTheDocument();
@ -65,52 +49,4 @@ describe('ESQLMenuPopover', () => {
await userEvent.click(screen.getByRole('button'));
expect(screen.queryByTestId('esql-recommended-queries')).toBeInTheDocument();
});
it('should fetch ESQL extensions when activeSolutionId and queryForRecommendedQueries are present and the popover is open', async () => {
const mockQueries = [
{ name: 'Count of logs', query: 'FROM logstash1 | STATS COUNT()' },
{ name: 'Average bytes', query: 'FROM logstash2 | STATS AVG(bytes) BY log.level' },
];
// Configure the mock to resolve with mockQueries
httpModule.http.get.mockResolvedValueOnce({ recommendedQueries: mockQueries });
renderESQLPopover(stubIndexPattern);
const esqlQuery = `FROM ${stubIndexPattern.name}`;
// Assert that http.get was called with the correct URL
await waitFor(() => {
expect(httpModule.http.get).toHaveBeenCalledTimes(1);
expect(httpModule.http.get).toHaveBeenCalledWith(
`/internal/esql_registry/extensions/oblt/${esqlQuery}`
);
});
// open the popover and check for recommended queries
await userEvent.click(screen.getByRole('button'));
expect(screen.queryByTestId('esql-recommended-queries')).toBeInTheDocument();
// Open the nested section to see the recommended queries
await waitFor(() => userEvent.click(screen.getByTestId('esql-recommended-queries')));
await waitFor(() => {
expect(screen.getByText('Count of logs')).toBeInTheDocument();
expect(screen.getByText('Average bytes')).toBeInTheDocument();
});
});
it('should handle API call failure gracefully', async () => {
// Configure the mock to reject with an error
httpModule.http.get.mockRejectedValueOnce(new Error('Network error'));
renderESQLPopover(stubIndexPattern);
// Assert that http.get was called (even if it failed)
await waitFor(() => {
expect(httpModule.http.get).toHaveBeenCalledTimes(1);
});
// The catch block does nothing, so we assert that no error is thrown
// and that the static recommended queries are still shown.
await userEvent.click(screen.getByRole('button'));
expect(screen.queryByTestId('esql-recommended-queries')).toBeInTheDocument();
});
});

View file

@ -6,27 +6,20 @@
* 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 React, { useMemo, useState, useCallback, useEffect, useRef } from 'react';
import React, { useMemo, useState, useCallback } from 'react';
import {
EuiPopover,
EuiButton,
type EuiContextMenuPanelDescriptor,
EuiContextMenuItem,
EuiContextMenu,
useEuiScrollBar,
} from '@elastic/eui';
import { isEqual } from 'lodash';
import { css } from '@emotion/react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n';
import type { DataView } from '@kbn/data-views-plugin/public';
import { FEEDBACK_LINK } from '@kbn/esql-utils';
import { type RecommendedQuery, REGISTRY_EXTENSIONS_ROUTE } from '@kbn/esql-types';
import {
getRecommendedQueries,
getRecommendedQueriesTemplatesFromExtensions,
} from '@kbn/esql-validation-autocomplete';
import { getRecommendedQueries } from '@kbn/esql-validation-autocomplete';
import { LanguageDocumentationFlyout } from '@kbn/language-documentation';
import type { IUnifiedSearchPluginServices } from '../types';
@ -42,65 +35,12 @@ export const ESQLMenuPopover: React.FC<ESQLMenuPopoverProps> = ({
onESQLQuerySubmit,
}) => {
const kibana = useKibana<IUnifiedSearchPluginServices>();
const { docLinks, http, chrome } = kibana.services;
const activeSolutionId = useObservable(chrome.getActiveSolutionNavId$());
const { docLinks } = kibana.services;
const [isESQLMenuPopoverOpen, setIsESQLMenuPopoverOpen] = useState(false);
const [isLanguageComponentOpen, setIsLanguageComponentOpen] = useState(false);
const [solutionsRecommendedQueries, setSolutionsRecommendedQueries] = useState<
RecommendedQuery[]
>([]);
const { queryForRecommendedQueries, timeFieldName } = useMemo(() => {
if (adHocDataview && typeof adHocDataview !== 'string') {
return {
queryForRecommendedQueries: `FROM ${adHocDataview.name}`,
timeFieldName:
adHocDataview.timeFieldName ?? adHocDataview.fields?.getByType('date')?.[0]?.name,
};
}
return {
queryForRecommendedQueries: '',
timeFieldName: undefined,
};
}, [adHocDataview]);
// Use a ref to store the *previous* fetched recommended queries
const lastFetchedQueries = useRef<RecommendedQuery[]>([]);
useEffect(() => {
let cancelled = false;
const getESQLExtensions = async () => {
if (!activeSolutionId || !queryForRecommendedQueries) {
return; // Don't fetch if we don't have the active solution or query
}
try {
const extensions: { recommendedQueries: RecommendedQuery[] } = await http.get(
`${REGISTRY_EXTENSIONS_ROUTE}${activeSolutionId}/${queryForRecommendedQueries}`
);
if (cancelled) return;
// Only update state if the new data is actually different from the *last successfully set* data
if (!isEqual(extensions.recommendedQueries, lastFetchedQueries.current)) {
setSolutionsRecommendedQueries(extensions.recommendedQueries);
lastFetchedQueries.current = extensions.recommendedQueries; // Update the ref with the new data
}
} catch (error) {
// Do nothing if the extensions are not available
}
};
getESQLExtensions();
return () => {
cancelled = true;
};
}, [activeSolutionId, http, queryForRecommendedQueries]);
const toggleLanguageComponent = useCallback(() => {
const toggleLanguageComponent = useCallback(async () => {
setIsLanguageComponentOpen(!isLanguageComponentOpen);
setIsESQLMenuPopoverOpen(false);
}, [isLanguageComponentOpen]);
@ -115,30 +55,18 @@ export const ESQLMenuPopover: React.FC<ESQLMenuPopoverProps> = ({
const esqlContextMenuPanels = useMemo(() => {
const recommendedQueries = [];
// If there are specific recommended queries for the current solution, process them.
if (solutionsRecommendedQueries.length) {
// Extract the core query templates by removing the 'FROM' clause.
const recommendedQueriesTemplatesFromExtensions =
getRecommendedQueriesTemplatesFromExtensions(solutionsRecommendedQueries);
if (adHocDataview && typeof adHocDataview !== 'string') {
const queryString = `FROM ${adHocDataview.name}`;
const timeFieldName =
adHocDataview.timeFieldName ?? adHocDataview.fields?.getByType('date')?.[0]?.name;
// Construct the full recommended queries by prepending the base 'FROM' command
// and add them to the main list of recommended queries.
recommendedQueries.push(
...recommendedQueriesTemplatesFromExtensions.map((template) => ({
label: template.label,
queryString: `${queryForRecommendedQueries}${template.text}`,
}))
...getRecommendedQueries({
fromCommand: queryString,
timeField: timeFieldName,
})
);
}
// Handle the static recommended queries, no solutions specific
if (queryForRecommendedQueries && timeFieldName) {
const recommendedQueriesFromStaticTemplates = getRecommendedQueries({
fromCommand: queryForRecommendedQueries,
timeField: timeFieldName,
});
recommendedQueries.push(...recommendedQueriesFromStaticTemplates);
}
const panels = [
{
id: 0,
@ -147,7 +75,7 @@ export const ESQLMenuPopover: React.FC<ESQLMenuPopoverProps> = ({
name: i18n.translate('unifiedSearch.query.queryBar.esqlMenu.quickReference', {
defaultMessage: 'Quick Reference',
}),
icon: 'nedocumentationsted', // Typo: Should be 'documentation'
icon: 'nedocumentationsted',
renderItem: () => (
<EuiContextMenuItem
key="quickReference"
@ -232,21 +160,7 @@ export const ESQLMenuPopover: React.FC<ESQLMenuPopoverProps> = ({
},
];
return panels as EuiContextMenuPanelDescriptor[];
}, [
docLinks.links.query.queryESQL,
onESQLQuerySubmit,
queryForRecommendedQueries,
timeFieldName,
toggleLanguageComponent,
solutionsRecommendedQueries, // This dependency is fine here, as it *uses* the state
]);
const esqlMenuPopoverStyles = css`
width: 240px;
max-height: 350px;
overflow-y: auto;
${useEuiScrollBar()};
`;
}, [adHocDataview, docLinks.links.query.queryESQL, onESQLQuerySubmit, toggleLanguageComponent]);
return (
<>
@ -265,7 +179,7 @@ export const ESQLMenuPopover: React.FC<ESQLMenuPopoverProps> = ({
}
panelProps={{
['data-test-subj']: 'esql-menu-popover',
css: esqlMenuPopoverStyles,
css: { width: 240 },
}}
isOpen={isESQLMenuPopoverOpen}
closePopover={() => setIsESQLMenuPopoverOpen(false)}

View file

@ -11,7 +11,6 @@ import { mockPersistedLogFactory } from './query_string_input.test.mocks';
import React from 'react';
import { mount, shallow } from 'enzyme';
import { BehaviorSubject } from 'rxjs';
import { render } from '@testing-library/react';
import { EMPTY } from 'rxjs';
@ -25,7 +24,6 @@ import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { unifiedSearchPluginMock } from '../mocks';
const startMock = coreMock.createStart();
startMock.chrome.getActiveSolutionNavId$.mockReturnValue(new BehaviorSubject('oblt'));
const mockTimeHistory = {
get: () => {

View file

@ -9,7 +9,7 @@
import React from 'react';
import SearchBar from './search_bar';
import { BehaviorSubject } from 'rxjs';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
import { I18nProvider } from '@kbn/i18n-react';
@ -84,10 +84,6 @@ function wrapSearchBarInContext(testProps: any) {
},
},
},
chrome: {
...startMock.chrome,
getActiveSolutionNavId$: jest.fn().mockReturnValue(new BehaviorSubject('oblt')),
},
uiSettings: startMock.uiSettings,
settings: startMock.settings,
savedObjects: startMock.savedObjects,

View file

@ -88,7 +88,6 @@ export interface IUnifiedSearchPluginServices extends Partial<CoreStart> {
autocomplete: AutocompleteStart;
};
appName: string;
chrome: CoreStart['chrome'];
uiSettings: CoreStart['uiSettings'];
savedObjects: CoreStart['savedObjects'];
notifications: CoreStart['notifications'];

View file

@ -48,8 +48,7 @@
"@kbn/esql-validation-autocomplete",
"@kbn/react-kibana-mount",
"@kbn/field-utils",
"@kbn/language-documentation",
"@kbn/esql-types"
"@kbn/language-documentation"
],
"exclude": [
"target/**/*",