[Logs Explorer] enabling redirect link to discover ESQL (#168002)

Closes https://github.com/elastic/kibana/issues/167023.

### Changes
- `EsqlSelector` was created. This component is in charge of rendering
the `Try ESQL` button.

#### Before
<img width="1905" alt="image"
src="9ffc304f-b68c-4871-8524-1239123f5826">

#### After
<img width="1900" alt="image"
src="b95e8739-b93b-47f7-884e-519b73b2ccda">

- It works as a link, so users are able to open it in a new tab if
desired
<img width="1899" alt="image"
src="d6390ba2-5010-4ed5-99cb-4c3f6e099dfe">

- It navigates to discover in `ES|QL` mode


1f083c2d-a06f-4f0b-9cbb-5958edcb9cce

##### For serverless
<img width="1896" alt="image"
src="48712921-85f7-4848-9e71-426bded04913">

- When `discover:enableESQL` is disabled
<img width="1901" alt="image"
src="4267e03b-7b46-4477-813b-65a8a04e6329">


### Missing
UI tests will come as part of a new PR.

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yngrid Coello 2023-10-04 18:05:29 +02:00 committed by GitHub
parent a867962ddb
commit eaf77786e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 193 additions and 16 deletions

View file

@ -84,6 +84,14 @@ export const noDataRetryLabel = i18n.translate('xpack.logExplorer.datasetSelecto
defaultMessage: 'Retry',
});
export const tryEsql = i18n.translate('xpack.logExplorer.datasetSelector.TryEsql', {
defaultMessage: 'Try ES|QL',
});
export const technicalPreview = i18n.translate('xpack.logExplorer.TechPreview', {
defaultMessage: 'Technical preview',
});
export const sortOptions = [
{
id: 'asc',

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import React, { useMemo } from 'react';
import styled from '@emotion/styled';
import { EuiContextMenu, EuiHorizontalRule, EuiTab, EuiTabs } from '@elastic/eui';
import styled from '@emotion/styled';
import React, { useMemo } from 'react';
import { useIntersectionRef } from '../../hooks/use_intersection_ref';
import { getDataViewTestSubj } from '../../utils/get_data_view_test_subj';
import {
dataViewsLabel,
DATA_VIEWS_PANEL_ID,
@ -23,6 +24,8 @@ import {
} from './constants';
import { useDatasetSelector } from './state_machine/use_dataset_selector';
import { DatasetsPopover } from './sub_components/datasets_popover';
import { DataViewsPanelTitle } from './sub_components/data_views_panel_title';
import { EsqlSelector } from './sub_components/esql_selector';
import { SearchControls } from './sub_components/search_controls';
import { SelectorActions } from './sub_components/selector_actions';
import { DatasetSelectorProps } from './types';
@ -32,8 +35,6 @@ import {
createIntegrationStatusItem,
createUncategorizedStatusItem,
} from './utils';
import { getDataViewTestSubj } from '../../utils/get_data_view_test_subj';
import { DataViewsPanelTitle } from './sub_components/data_views_panel_title';
export function DatasetSelector({
datasets,
@ -41,8 +42,10 @@ export function DatasetSelector({
datasetsError,
dataViews,
dataViewsError,
discoverEsqlUrlProps,
integrations,
integrationsError,
isEsqlEnabled,
isLoadingDataViews,
isLoadingIntegrations,
isLoadingUncategorized,
@ -278,6 +281,7 @@ export function DatasetSelector({
data-test-subj="dataViewsContextMenu"
size="s"
/>
{isEsqlEnabled && <EsqlSelector {...discoverEsqlUrlProps} />}
</DatasetsPopover>
);
}

View file

@ -0,0 +1,26 @@
/*
* 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 { EuiBadge, EuiButton, EuiHorizontalRule } from '@elastic/eui';
import React from 'react';
import { getRouterLinkProps } from '../../../utils/get_router_link_props';
import { DiscoverEsqlUrlProps } from '../../../hooks/use_esql';
import { technicalPreview, tryEsql } from '../constants';
export const EsqlSelector = (props: DiscoverEsqlUrlProps) => {
const linkProps = getRouterLinkProps(props);
return (
<>
<EuiHorizontalRule margin="none" />
<EuiButton {...linkProps} color="success" size="s" fullWidth data-test-subj="esqlLink">
{tryEsql}
<EuiBadge color="hollow">{technicalPreview}</EuiBadge>
</EuiButton>
</>
);
};

View file

@ -27,6 +27,7 @@ import {
UNCATEGORIZED_TAB_ID,
} from './constants';
import { LoadDataViews, ReloadDataViews, SearchDataViews } from '../../hooks/use_data_views';
import { DiscoverEsqlUrlProps } from '../../hooks/use_esql';
export interface DatasetSelectorProps {
/* The generic data stream list */
@ -39,6 +40,8 @@ export interface DatasetSelectorProps {
dataViews: DataViewListItem[] | null;
/* Any error occurred to show when the user preview the data views */
dataViewsError: Error | null;
/* url props to navigate to discover ES|QL */
discoverEsqlUrlProps: DiscoverEsqlUrlProps;
/* The integrations list, each integration includes its data streams */
integrations: Integration[] | null;
/* Any error occurred to show when the user preview the integrations */
@ -48,6 +51,8 @@ export interface DatasetSelectorProps {
isLoadingIntegrations: boolean;
isLoadingUncategorized: boolean;
isSearchingIntegrations: boolean;
/* Flag for determining whether ESQL is enabled or not */
isEsqlEnabled: boolean;
/* Triggered when retrying to load the data views */
onDataViewsReload: ReloadDataViews;
/* Triggered when selecting a data view */

View file

@ -5,16 +5,17 @@
* 2.0.
*/
import React from 'react';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import React from 'react';
import { DatasetSelector } from '../components/dataset_selector';
import { DatasetsProvider, useDatasetsContext } from '../hooks/use_datasets';
import { useDatasetSelection } from '../hooks/use_dataset_selection';
import { DataViewsProvider, useDataViewsContext } from '../hooks/use_data_views';
import { useEsql } from '../hooks/use_esql';
import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations';
import { IDatasetsClient } from '../services/datasets';
import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
import { useDatasetSelection } from '../hooks/use_dataset_selection';
import { DataViewsProvider, useDataViewsContext } from '../hooks/use_data_views';
interface CustomDatasetSelectorProps {
logExplorerProfileStateService: LogExplorerProfileStateService;
@ -59,6 +60,8 @@ export const CustomDatasetSelector = withProviders(({ logExplorerProfileStateSer
sortDataViews,
} = useDataViewsContext();
const { isEsqlEnabled, discoverEsqlUrlProps } = useEsql({ datasetSelection });
return (
<DatasetSelector
datasets={datasets}
@ -66,15 +69,17 @@ export const CustomDatasetSelector = withProviders(({ logExplorerProfileStateSer
datasetsError={datasetsError}
dataViews={dataViews}
dataViewsError={dataViewsError}
discoverEsqlUrlProps={discoverEsqlUrlProps}
integrations={integrations}
integrationsError={integrationsError}
isEsqlEnabled={isEsqlEnabled}
isLoadingDataViews={isLoadingDataViews}
isLoadingIntegrations={isLoadingIntegrations}
isLoadingUncategorized={isLoadingUncategorized}
isSearchingIntegrations={isSearchingIntegrations}
onDataViewSelection={selectDataView}
onDataViewsReload={reloadDataViews}
onDataViewsSearch={searchDataViews}
onDataViewSelection={selectDataView}
onDataViewsSort={sortDataViews}
onDataViewsTabClick={loadDataViews}
onIntegrationsLoadMore={loadMore}

View file

@ -12,6 +12,7 @@ import { dynamic } from '../utils/dynamic';
import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
import { LogExplorerStateContainer } from '../components/log_explorer';
import { LogExplorerStartDeps } from '../types';
import { useKibanaContextForPluginProvider } from '../utils/use_kibana';
const LazyCustomDatasetSelector = dynamic(() => import('./custom_dataset_selector'));
const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters'));
@ -69,14 +70,20 @@ export const createLogExplorerProfileCustomizations =
*/
customizations.set({
id: 'search_bar',
CustomDataViewPicker: () => (
<LazyCustomDatasetSelector
datasetsClient={datasetsClient}
dataViews={dataViews}
discover={discover}
logExplorerProfileStateService={logExplorerProfileStateService}
/>
),
CustomDataViewPicker: () => {
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(core, plugins);
return (
<KibanaContextProviderForPlugin>
<LazyCustomDatasetSelector
datasetsClient={datasetsClient}
dataViews={dataViews}
discover={discover}
logExplorerProfileStateService={logExplorerProfileStateService}
/>
</KibanaContextProviderForPlugin>
);
},
PrependFilterBar: () => (
<LazyCustomDatasetFilters
logExplorerProfileStateService={logExplorerProfileStateService}

View file

@ -0,0 +1,52 @@
/*
* 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 { DatasetSelection } from '../../common/dataset_selection';
import { useKibanaContextForPlugin } from '../utils/use_kibana';
export interface DiscoverEsqlUrlProps {
href?: string;
onClick: () => void;
}
export interface UseEsqlResult {
isEsqlEnabled: boolean;
discoverEsqlUrlProps: DiscoverEsqlUrlProps;
}
interface EsqlContextDeps {
datasetSelection: DatasetSelection;
}
export const useEsql = ({ datasetSelection }: EsqlContextDeps): UseEsqlResult => {
const {
services: { uiSettings, discover },
} = useKibanaContextForPlugin();
const isEsqlEnabled = uiSettings?.get('discover:enableESQL');
const discoverLinkParams = {
query: {
esql: `from ${datasetSelection.selection.dataset.name} | limit 10`,
},
};
const href = discover.locator?.useUrl(discoverLinkParams);
const onClick = () => {
discover.locator?.navigate(discoverLinkParams);
};
return {
// Data
isEsqlEnabled,
discoverEsqlUrlProps: {
href,
onClick,
},
};
};

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
interface GetRouterLinkPropsDeps {
href?: string;
onClick(): void;
}
const isModifiedEvent = (event: React.MouseEvent<HTMLAnchorElement>) =>
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
const isLeftClickEvent = (event: React.MouseEvent<HTMLAnchorElement>) => event.button === 0;
export const getRouterLinkProps = ({ href, onClick }: GetRouterLinkPropsDeps) => {
const guardedClickHandler = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (event.defaultPrevented) {
return;
}
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
return;
}
// Prevent regular link behavior, which causes a browser refresh.
event.preventDefault();
onClick();
};
return { href, onClick: guardedClickHandler };
};

View file

@ -0,0 +1,35 @@
/*
* 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 { CoreStart } from '@kbn/core/public';
import {
createKibanaReactContext,
KibanaReactContextValue,
useKibana,
} from '@kbn/kibana-react-plugin/public';
import { useMemo } from 'react';
import { LogExplorerStartDeps } from '../types';
export type PluginKibanaContextValue = CoreStart & LogExplorerStartDeps;
export const createKibanaContextForPlugin = (core: CoreStart, plugins: LogExplorerStartDeps) =>
createKibanaReactContext<PluginKibanaContextValue>({
...core,
...plugins,
});
export const useKibanaContextForPlugin =
useKibana as () => KibanaReactContextValue<PluginKibanaContextValue>;
export const useKibanaContextForPluginProvider = (
core: CoreStart,
plugins: LogExplorerStartDeps
) => {
const { Provider } = useMemo(() => createKibanaContextForPlugin(core, plugins), [core, plugins]);
return Provider;
};