mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
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:
parent
25fe1fa0e2
commit
a29fee8922
12 changed files with 27 additions and 217 deletions
|
@ -34,5 +34,3 @@ export {
|
|||
type InferenceEndpointsAutocompleteResult,
|
||||
type InferenceEndpointAutocompleteItem,
|
||||
} from './src/inference_endpoint_autocomplete_types';
|
||||
|
||||
export { REGISTRY_EXTENSIONS_ROUTE } from './src/constants';
|
||||
|
|
|
@ -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/';
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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: () => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue