mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ES|QL] Present ES|QL as an equal to data views on the "no data views" screen (#194077)
## Summary Resolves https://github.com/elastic/kibana/issues/176291 ### Screenshots #### Discover/Dashboard/Visualize <img width="1517" alt="image" src="https://github.com/user-attachments/assets/c8fb4faf-8d2d-4043-a757-6f092d1fceb3"> #### Stack Management > Data view <img width="1519" alt="image" src="https://github.com/user-attachments/assets/bb3f28bd-0ce9-4c49-ace1-1be875e17ba3"> #### If User does not have privilege to create a Data View <img width="1336" alt="image" src="https://github.com/user-attachments/assets/bb3f4cbc-8213-41c0-bf09-bd47a9d264e4"> ### Checklist Delete any items that are not applicable to this PR. - [x] Use a new SVG resource for the ES|QL illustration - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Co-authored-by: Andrea Del Rio <delrio.andre@gmail.com>
This commit is contained in:
parent
3fa70e122c
commit
56e1e68b30
36 changed files with 2133 additions and 235 deletions
|
@ -22,12 +22,14 @@ import { getHasApiKeys$ } from '../lib/get_has_api_keys';
|
|||
export interface Props {
|
||||
/** Handler for successfully creating a new data view. */
|
||||
onDataViewCreated: (dataView: unknown) => void;
|
||||
/** Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover. */
|
||||
onESQLNavigationComplete?: () => void;
|
||||
/** if set to true allows creation of an ad-hoc dataview from data view editor */
|
||||
allowAdHocDataView?: boolean;
|
||||
/** if the kibana instance is customly branded */
|
||||
showPlainSpinner: boolean;
|
||||
/** If the cluster has data, this handler allows the user to try ES|QL */
|
||||
onTryESQL?: () => void;
|
||||
/** Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover. */
|
||||
onESQLNavigationComplete?: () => void;
|
||||
}
|
||||
|
||||
type AnalyticsNoDataPageProps = Props &
|
||||
|
@ -119,9 +121,10 @@ const flavors: {
|
|||
*/
|
||||
export const AnalyticsNoDataPage: React.FC<AnalyticsNoDataPageProps> = ({
|
||||
onDataViewCreated,
|
||||
onESQLNavigationComplete,
|
||||
allowAdHocDataView,
|
||||
showPlainSpinner,
|
||||
onTryESQL,
|
||||
onESQLNavigationComplete,
|
||||
...services
|
||||
}) => {
|
||||
const { prependBasePath, kibanaGuideDocLink, getHttp: get, pageFlavor } = services;
|
||||
|
@ -138,8 +141,9 @@ export const AnalyticsNoDataPage: React.FC<AnalyticsNoDataPageProps> = ({
|
|||
{...{
|
||||
noDataConfig,
|
||||
onDataViewCreated,
|
||||
onESQLNavigationComplete,
|
||||
allowAdHocDataView,
|
||||
onTryESQL,
|
||||
onESQLNavigationComplete,
|
||||
showPlainSpinner,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -29,8 +29,8 @@ export default {
|
|||
|
||||
export const Analytics = (params: AnalyticsNoDataPageStorybookParams) => {
|
||||
return (
|
||||
<AnalyticsNoDataPageProvider {...mock.getServices(params)}>
|
||||
<Component {...mock.getProps()} />
|
||||
<AnalyticsNoDataPageProvider {...mock.getProps(params)} {...mock.getServices(params)}>
|
||||
<Component {...mock.getProps(params)} />
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
getAnalyticsNoDataPageServicesMock,
|
||||
getAnalyticsNoDataPageServicesMockWithCustomBranding,
|
||||
} from '@kbn/shared-ux-page-analytics-no-data-mocks';
|
||||
import { NoDataViewsPrompt } from '@kbn/shared-ux-prompt-no-data-views';
|
||||
|
||||
import { AnalyticsNoDataPageProvider } from './services';
|
||||
import { AnalyticsNoDataPage as Component } from './analytics_no_data_page.component';
|
||||
|
@ -29,28 +30,86 @@ describe('AnalyticsNoDataPage', () => {
|
|||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', async () => {
|
||||
const component = mountWithIntl(
|
||||
<AnalyticsNoDataPageProvider {...services}>
|
||||
<AnalyticsNoDataPage onDataViewCreated={onDataViewCreated} allowAdHocDataView={true} />
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
describe('loading state', () => {
|
||||
it('renders correctly', async () => {
|
||||
const component = mountWithIntl(
|
||||
<AnalyticsNoDataPageProvider {...services}>
|
||||
<AnalyticsNoDataPage onDataViewCreated={onDataViewCreated} allowAdHocDataView={true} />
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
|
||||
await act(() => new Promise(setImmediate));
|
||||
await act(() => new Promise(setImmediate));
|
||||
|
||||
expect(component.find(Component).length).toBe(1);
|
||||
expect(component.find(Component).props().onDataViewCreated).toBe(onDataViewCreated);
|
||||
expect(component.find(Component).props().allowAdHocDataView).toBe(true);
|
||||
expect(component.find(Component).length).toBe(1);
|
||||
expect(component.find(Component).props().onDataViewCreated).toBe(onDataViewCreated);
|
||||
expect(component.find(Component).props().allowAdHocDataView).toBe(true);
|
||||
});
|
||||
|
||||
it('passes correct boolean value to showPlainSpinner', async () => {
|
||||
const component = mountWithIntl(
|
||||
<AnalyticsNoDataPageProvider {...servicesWithCustomBranding}>
|
||||
<AnalyticsNoDataPage onDataViewCreated={onDataViewCreated} allowAdHocDataView={true} />
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
component.update();
|
||||
});
|
||||
|
||||
expect(component.find(Component).length).toBe(1);
|
||||
expect(component.find(Component).props().showPlainSpinner).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes correct boolean value to showPlainSpinner', () => {
|
||||
const component = mountWithIntl(
|
||||
<AnalyticsNoDataPageProvider {...servicesWithCustomBranding}>
|
||||
<AnalyticsNoDataPage onDataViewCreated={onDataViewCreated} allowAdHocDataView={true} />
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
describe('with ES data', () => {
|
||||
jest.spyOn(services, 'hasESData').mockResolvedValue(true);
|
||||
jest.spyOn(services, 'hasUserDataView').mockResolvedValue(false);
|
||||
|
||||
expect(component.find(Component).length).toBe(1);
|
||||
expect(component.find(Component).props().showPlainSpinner).toBe(true);
|
||||
it('renders the prompt to create a data view', async () => {
|
||||
const onTryESQL = jest.fn();
|
||||
|
||||
await act(async () => {
|
||||
const component = mountWithIntl(
|
||||
<AnalyticsNoDataPageProvider {...services}>
|
||||
<AnalyticsNoDataPage
|
||||
onDataViewCreated={onDataViewCreated}
|
||||
allowAdHocDataView={true}
|
||||
onTryESQL={onTryESQL}
|
||||
/>
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
|
||||
await new Promise(setImmediate);
|
||||
component.update();
|
||||
|
||||
expect(component.find(Component).length).toBe(1);
|
||||
expect(component.find(NoDataViewsPrompt).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the prompt to create a data view with a custom onTryESQL action', async () => {
|
||||
const onTryESQL = jest.fn();
|
||||
|
||||
await act(async () => {
|
||||
const component = mountWithIntl(
|
||||
<AnalyticsNoDataPageProvider {...services}>
|
||||
<AnalyticsNoDataPage
|
||||
onDataViewCreated={onDataViewCreated}
|
||||
allowAdHocDataView={true}
|
||||
onTryESQL={onTryESQL}
|
||||
/>
|
||||
</AnalyticsNoDataPageProvider>
|
||||
);
|
||||
|
||||
await new Promise(setImmediate);
|
||||
component.update();
|
||||
|
||||
const tryESQLLink = component.find('button[data-test-subj="tryESQLLink"]');
|
||||
expect(tryESQLLink.length).toBe(1);
|
||||
tryESQLLink.simulate('click');
|
||||
|
||||
expect(onTryESQL).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,8 +20,9 @@ import { AnalyticsNoDataPage as Component } from './analytics_no_data_page.compo
|
|||
*/
|
||||
export const AnalyticsNoDataPage = ({
|
||||
onDataViewCreated,
|
||||
onESQLNavigationComplete,
|
||||
allowAdHocDataView,
|
||||
onTryESQL,
|
||||
onESQLNavigationComplete,
|
||||
}: AnalyticsNoDataPageProps) => {
|
||||
const { customBranding, ...services } = useServices();
|
||||
const showPlainSpinner = useObservable(customBranding.hasCustomBranding$) ?? false;
|
||||
|
@ -33,6 +34,7 @@ export const AnalyticsNoDataPage = ({
|
|||
allowAdHocDataView={allowAdHocDataView}
|
||||
onDataViewCreated={onDataViewCreated}
|
||||
onESQLNavigationComplete={onESQLNavigationComplete}
|
||||
onTryESQL={onTryESQL}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"@kbn/i18n-react",
|
||||
"@kbn/core-http-browser",
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/shared-ux-prompt-no-data-views",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -18,9 +18,14 @@ import type {
|
|||
} from '@kbn/shared-ux-page-analytics-no-data-types';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
interface PropArguments {
|
||||
useCustomOnTryESQL: boolean;
|
||||
}
|
||||
|
||||
type ServiceArguments = Pick<AnalyticsNoDataPageServices, 'kibanaGuideDocLink' | 'customBranding'>;
|
||||
|
||||
export type Params = ArgumentParams<{}, ServiceArguments> & KibanaNoDataPageStorybookParams;
|
||||
export type Params = ArgumentParams<PropArguments, ServiceArguments> &
|
||||
KibanaNoDataPageStorybookParams;
|
||||
|
||||
const kibanaNoDataMock = new KibanaNoDataPageStorybookMock();
|
||||
|
||||
|
@ -30,7 +35,13 @@ export class StorybookMock extends AbstractStorybookMock<
|
|||
{},
|
||||
ServiceArguments
|
||||
> {
|
||||
propArguments = {};
|
||||
propArguments = {
|
||||
// requires hasESData to be toggled to true
|
||||
useCustomOnTryESQL: {
|
||||
control: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
};
|
||||
serviceArguments = {
|
||||
kibanaGuideDocLink: {
|
||||
control: 'text',
|
||||
|
@ -59,9 +70,10 @@ export class StorybookMock extends AbstractStorybookMock<
|
|||
};
|
||||
}
|
||||
|
||||
getProps() {
|
||||
getProps(params: Params) {
|
||||
return {
|
||||
onDataViewCreated: action('onDataViewCreated'),
|
||||
onTryESQL: params.useCustomOnTryESQL ? action('onTryESQL-from-props') : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ export interface AnalyticsNoDataPageProps {
|
|||
onDataViewCreated: (dataView: unknown) => void;
|
||||
/** if set to true allows creation of an ad-hoc data view from data view editor */
|
||||
allowAdHocDataView?: boolean;
|
||||
/** If the cluster has data, this handler allows the user to try ES|QL */
|
||||
onTryESQL?: () => void;
|
||||
/** Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover. */
|
||||
onESQLNavigationComplete?: () => void;
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ import { useServices } from './services';
|
|||
*/
|
||||
export const KibanaNoDataPage = ({
|
||||
onDataViewCreated,
|
||||
onESQLNavigationComplete,
|
||||
noDataConfig,
|
||||
allowAdHocDataView,
|
||||
onTryESQL,
|
||||
onESQLNavigationComplete,
|
||||
showPlainSpinner,
|
||||
}: KibanaNoDataPageProps) => {
|
||||
// These hooks are temporary, until this component is moved to a package.
|
||||
|
@ -58,8 +59,9 @@ export const KibanaNoDataPage = ({
|
|||
return (
|
||||
<NoDataViewsPrompt
|
||||
onDataViewCreated={onDataViewCreated}
|
||||
onESQLNavigationComplete={onESQLNavigationComplete}
|
||||
allowAdHocDataView={allowAdHocDataView}
|
||||
onTryESQL={onTryESQL}
|
||||
onESQLNavigationComplete={onESQLNavigationComplete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ export interface KibanaNoDataPageProps {
|
|||
allowAdHocDataView?: boolean;
|
||||
/** Set to true if the kibana is customly branded */
|
||||
showPlainSpinner: boolean;
|
||||
/** If the cluster has data, this handler allows the user to try ES|QL */
|
||||
onTryESQL?: () => void;
|
||||
/** Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover. */
|
||||
onESQLNavigationComplete?: () => void;
|
||||
}
|
||||
|
|
|
@ -1,76 +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".
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
interface NoDataButtonProps {
|
||||
onClickCreate: (() => void) | undefined;
|
||||
canCreateNewDataView: boolean;
|
||||
onTryESQL?: () => void;
|
||||
esqlDocLink?: string;
|
||||
}
|
||||
|
||||
const createDataViewText = i18n.translate('sharedUXPackages.noDataViewsPrompt.addDataViewText', {
|
||||
defaultMessage: 'Create data view',
|
||||
});
|
||||
|
||||
export const NoDataButtonLink = ({
|
||||
onClickCreate,
|
||||
canCreateNewDataView,
|
||||
onTryESQL,
|
||||
esqlDocLink,
|
||||
}: NoDataButtonProps) => {
|
||||
if (!onTryESQL && !canCreateNewDataView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{canCreateNewDataView && (
|
||||
<EuiButton
|
||||
onClick={onClickCreate}
|
||||
iconType="plusInCircle"
|
||||
fill={true}
|
||||
data-test-subj="createDataViewButton"
|
||||
>
|
||||
{createDataViewText}
|
||||
</EuiButton>
|
||||
)}
|
||||
{canCreateNewDataView && onTryESQL && <EuiSpacer />}
|
||||
{onTryESQL && (
|
||||
<EuiText size="xs" color={'subdued'}>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.no_data_views.esqlMessage"
|
||||
defaultMessage="Alternatively, you can query your data directly using ES|QL. {docsLink}"
|
||||
values={{
|
||||
docsLink: esqlDocLink && (
|
||||
<EuiLink href={esqlDocLink} target="_blank">
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.no_data_views.esqlDocsLink"
|
||||
defaultMessage="Learn more."
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiButton color="success" onClick={onTryESQL} size="s" data-test-subj="tryESQLLink">
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.no_data_views.esqlButtonLabel"
|
||||
defaultMessage="Language: ES|QL"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -26,5 +26,14 @@ export const DataViewIllustration = () => {
|
|||
}
|
||||
`;
|
||||
|
||||
return <img src={svg} css={css} alt="Data view illustration" />;
|
||||
return (
|
||||
<img
|
||||
src={svg}
|
||||
css={css}
|
||||
alt="Data view illustration"
|
||||
data-test-subj="DataViewIllustration"
|
||||
width="110"
|
||||
height="100"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,9 +13,10 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
|
||||
interface Props {
|
||||
href: string;
|
||||
['data-test-subj']?: string;
|
||||
}
|
||||
|
||||
export function DocumentationLink({ href }: Props) {
|
||||
export function DocumentationLink({ href, ['data-test-subj']: dataTestSubj }: Props) {
|
||||
return (
|
||||
<dl>
|
||||
<EuiTitle size="xxs">
|
||||
|
@ -28,7 +29,7 @@ export function DocumentationLink({ href }: Props) {
|
|||
</EuiTitle>
|
||||
 
|
||||
<dd className="eui-displayInline">
|
||||
<EuiLink href={href} target="_blank" external>
|
||||
<EuiLink href={href} target="_blank" data-test-subj={dataTestSubj} external>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.readDocumentation"
|
||||
defaultMessage="Read the docs"
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 622 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import png from './esql_illustration.svg';
|
||||
|
||||
export const EsqlIllustration = () => {
|
||||
return (
|
||||
<img
|
||||
src={png}
|
||||
alt="ES|QL illustration"
|
||||
data-test-subj="EsqlIllustration"
|
||||
width="140"
|
||||
height="92"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { EuiButton, EuiCard } from '@elastic/eui';
|
||||
import { NoDataViewsPrompt } from './no_data_views.component';
|
||||
import { DocumentationLink } from './documentation_link';
|
||||
|
||||
|
@ -19,36 +19,64 @@ describe('<NoDataViewsPromptComponent />', () => {
|
|||
<NoDataViewsPrompt
|
||||
onClickCreate={jest.fn()}
|
||||
canCreateNewDataView={true}
|
||||
dataViewsDocLink={'dummy'}
|
||||
dataViewsDocLink="doc-link-data-views"
|
||||
esqlDocLink="doc-link-esql"
|
||||
onTryESQL={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(component.find(EuiEmptyPrompt).length).toBe(1);
|
||||
expect(component.find(EuiButton).length).toBe(1);
|
||||
expect(component.find(DocumentationLink).length).toBe(1);
|
||||
expect(component.find(EuiCard).length).toBe(2);
|
||||
expect(component.find(EuiButton).length).toBe(2);
|
||||
expect(component.find(DocumentationLink).length).toBe(2);
|
||||
|
||||
expect(component.find('EuiButton[data-test-subj="createDataViewButton"]').length).toBe(1);
|
||||
expect(component.find('DocumentationLink[data-test-subj="docLinkDataViews"]').length).toBe(1);
|
||||
|
||||
expect(component.find('EuiButton[data-test-subj="tryESQLLink"]').length).toBe(1);
|
||||
expect(component.find('DocumentationLink[data-test-subj="docLinkEsql"]').length).toBe(1);
|
||||
});
|
||||
|
||||
test('does not render button if canCreateNewDataViews is false', () => {
|
||||
test('does not render "Create data view" button if canCreateNewDataViews is false', () => {
|
||||
const component = mountWithIntl(<NoDataViewsPrompt canCreateNewDataView={false} />);
|
||||
|
||||
expect(component.find(EuiButton).length).toBe(0);
|
||||
expect(component.find('EuiButton[data-test-subj="createDataViewButton"]').length).toBe(0);
|
||||
});
|
||||
|
||||
test('does not documentation link if linkToDocumentation is not provided', () => {
|
||||
test('does not render documentation links if links to documentation are not provided', () => {
|
||||
const component = mountWithIntl(
|
||||
<NoDataViewsPrompt onClickCreate={jest.fn()} canCreateNewDataView={true} />
|
||||
);
|
||||
|
||||
expect(component.find(DocumentationLink).length).toBe(0);
|
||||
expect(component.find('DocumentationLink[data-test-subj="docLinkDataViews"]').length).toBe(0);
|
||||
expect(component.find('DocumentationLink[data-test-subj="docLinkEsql"]').length).toBe(0);
|
||||
});
|
||||
|
||||
test('onClickCreate', () => {
|
||||
const onClickCreate = jest.fn();
|
||||
const component = mountWithIntl(
|
||||
<NoDataViewsPrompt canCreateNewDataView={true} onClickCreate={onClickCreate} />
|
||||
<NoDataViewsPrompt
|
||||
canCreateNewDataView={true}
|
||||
onClickCreate={onClickCreate}
|
||||
dataViewsDocLink="doc-link/data-view"
|
||||
/>
|
||||
);
|
||||
|
||||
component.find('button').simulate('click');
|
||||
component.find('button[data-test-subj="createDataViewButton"]').simulate('click');
|
||||
|
||||
expect(onClickCreate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('onClickTryEsql', () => {
|
||||
const onClickTryEsql = jest.fn();
|
||||
const component = mountWithIntl(
|
||||
<NoDataViewsPrompt
|
||||
canCreateNewDataView={false}
|
||||
onTryESQL={onClickTryEsql}
|
||||
esqlDocLink="doc-link/esql"
|
||||
/>
|
||||
);
|
||||
|
||||
component.find('button[data-test-subj="tryESQLLink"]').simulate('click');
|
||||
|
||||
expect(onClickTryEsql).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,19 +7,159 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import React from 'react';
|
||||
|
||||
import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTextAlign,
|
||||
EuiToolTip,
|
||||
useEuiPaddingCSS,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import { NoDataViewsPromptComponentProps } from '@kbn/shared-ux-prompt-no-data-views-types';
|
||||
|
||||
import { DocumentationLink } from './documentation_link';
|
||||
import { NoDataButtonLink } from './actions';
|
||||
import { DataViewIllustration } from './data_view_illustration';
|
||||
import { EsqlIllustration } from './esql_illustration';
|
||||
|
||||
// Using raw value because it is content dependent
|
||||
const MAX_WIDTH = 830;
|
||||
// max width value to use in pixels
|
||||
const MAX_WIDTH = 770;
|
||||
|
||||
const PromptAddDataViews = ({
|
||||
onClickCreate,
|
||||
canCreateNewDataView,
|
||||
dataViewsDocLink,
|
||||
emptyPromptColor,
|
||||
}: Pick<
|
||||
NoDataViewsPromptComponentProps,
|
||||
'onClickCreate' | 'canCreateNewDataView' | 'dataViewsDocLink' | 'emptyPromptColor'
|
||||
>) => {
|
||||
const icon = <DataViewIllustration />;
|
||||
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.createDataView"
|
||||
defaultMessage="Create a data view"
|
||||
/>
|
||||
);
|
||||
|
||||
const description = (
|
||||
<>
|
||||
{canCreateNewDataView ? (
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.dataViewExplanation"
|
||||
defaultMessage="Data views identify the Elasticsearch data you want to explore. You can point data views to one or more data streams, indices, and index aliases, such as your log data from yesterday, or all indices that contain your log data."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.noPermission.dataViewExplanation"
|
||||
defaultMessage="Data views identify the Elasticsearch data that you want to explore. To create data views, ask your administrator for the required permissions."
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const footer = dataViewsDocLink ? (
|
||||
<>
|
||||
{canCreateNewDataView ? (
|
||||
<EuiButton onClick={onClickCreate} fill={true} data-test-subj="createDataViewButton">
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.addDataViewText"
|
||||
defaultMessage="Create data view"
|
||||
/>
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiToolTip
|
||||
position="right"
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.addDataViewTooltipNoPrivilege"
|
||||
defaultMessage="Ask your administrator for the permissions required to create a data view."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButton disabled data-test-subj="createDataViewButton">
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.addDataViewTextNoPrivilege"
|
||||
defaultMessage="Create data view"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
<EuiHorizontalRule />
|
||||
<DocumentationLink href={dataViewsDocLink} data-test-subj="docLinkDataViews" />
|
||||
</>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<EuiCard
|
||||
hasBorder={true}
|
||||
data-test-subj="noDataViewsPromptCreateDataView"
|
||||
display={emptyPromptColor}
|
||||
{...{ icon, title, description, footer }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const PromptTryEsql = ({
|
||||
onTryESQL,
|
||||
esqlDocLink,
|
||||
emptyPromptColor,
|
||||
}: Pick<
|
||||
NoDataViewsPromptComponentProps,
|
||||
'onClickCreate' | 'onTryESQL' | 'esqlDocLink' | 'emptyPromptColor'
|
||||
>) => {
|
||||
if (!onTryESQL) {
|
||||
// we need to handle the case where the Try ES|QL click handler is not set because
|
||||
// onTryESQL is set via a useEffect that has asynchronous dependencies
|
||||
return null;
|
||||
}
|
||||
|
||||
const icon = <EsqlIllustration />;
|
||||
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.esqlPanel.title"
|
||||
defaultMessage="Query your data with ES|QL"
|
||||
/>
|
||||
);
|
||||
|
||||
const description = (
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.esqlExplanation"
|
||||
defaultMessage="ES|QL is a next-generation piped query language and compute engine developed by Elastic for filtering, transforming, and analyzing data. ES|QL helps streamline your workflows for fast, efficient data processing."
|
||||
/>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<EuiButton onClick={onTryESQL} fill={true} data-test-subj="tryESQLLink">
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.tryEsqlText"
|
||||
defaultMessage="Try ES|QL"
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiHorizontalRule />
|
||||
{esqlDocLink && <DocumentationLink href={esqlDocLink} data-test-subj="docLinkEsql" />}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiCard
|
||||
hasBorder={true}
|
||||
data-test-subj="noDataViewsPromptTryEsql"
|
||||
display={emptyPromptColor}
|
||||
{...{ icon, title, description, footer }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A presentational component that is shown in cases when there are no data views created yet.
|
||||
|
@ -32,70 +172,57 @@ export const NoDataViewsPrompt = ({
|
|||
esqlDocLink,
|
||||
emptyPromptColor = 'plain',
|
||||
}: NoDataViewsPromptComponentProps) => {
|
||||
const title = canCreateNewDataView ? (
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.youHaveData"
|
||||
defaultMessage="You have data in Elasticsearch."
|
||||
/>
|
||||
<br />
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.nowCreate"
|
||||
defaultMessage="Now, create a data view."
|
||||
/>
|
||||
</h2>
|
||||
) : (
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.noPermission.title"
|
||||
defaultMessage="You need permission to create data views"
|
||||
/>
|
||||
</h2>
|
||||
);
|
||||
|
||||
const body = canCreateNewDataView ? (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.dataViewExplanation"
|
||||
defaultMessage="Data views identify the Elasticsearch data you want to explore. You can point data views to one or more data streams, indices, and index aliases, such as your log data from yesterday, or all indices that contain your log data."
|
||||
/>
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.noPermission.dataViewExplanation"
|
||||
defaultMessage="Data views identify the Elasticsearch data that you want to explore. To create data views, ask your administrator for the required permissions."
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
|
||||
const footer = dataViewsDocLink ? <DocumentationLink href={dataViewsDocLink} /> : undefined;
|
||||
|
||||
// Load this illustration lazily
|
||||
const Illustration = withSuspense(
|
||||
React.lazy(() =>
|
||||
import('./data_view_illustration').then(({ DataViewIllustration }) => {
|
||||
return { default: DataViewIllustration };
|
||||
})
|
||||
),
|
||||
<EuiPanel color="subdued" style={{ width: 226, height: 206 }} />
|
||||
);
|
||||
|
||||
const icon = <Illustration />;
|
||||
const actions = (
|
||||
<NoDataButtonLink {...{ onClickCreate, canCreateNewDataView, onTryESQL, esqlDocLink }} />
|
||||
);
|
||||
const cssStyles = [
|
||||
css`
|
||||
max-width: ${MAX_WIDTH}px;
|
||||
`,
|
||||
useEuiPaddingCSS('top').m,
|
||||
useEuiPaddingCSS('right').m,
|
||||
useEuiPaddingCSS('left').m,
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceEvenly"
|
||||
data-test-subj="noDataViewsPrompt"
|
||||
layout="horizontal"
|
||||
css={css`
|
||||
max-width: ${MAX_WIDTH}px !important; // Necessary to override EuiEmptyPrompt to fit content
|
||||
flex-grow: 0;
|
||||
`}
|
||||
color={emptyPromptColor}
|
||||
{...{ actions, icon, title, body, footer }}
|
||||
/>
|
||||
>
|
||||
<EuiFlexItem css={cssStyles}>
|
||||
<EuiText>
|
||||
<EuiTextAlign textAlign="center">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="sharedUXPackages.noDataViewsPrompt.youHaveData"
|
||||
defaultMessage="How do you want to explore your Elasticsearch data?"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTextAlign>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<PromptAddDataViews
|
||||
{...{
|
||||
onClickCreate,
|
||||
canCreateNewDataView,
|
||||
dataViewsDocLink,
|
||||
emptyPromptColor,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<PromptTryEsql
|
||||
{...{
|
||||
onTryESQL,
|
||||
esqlDocLink,
|
||||
emptyPromptColor,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,12 +27,15 @@ type CloseDataViewEditorFn = ReturnType<NoDataViewsPromptServices['openDataViewE
|
|||
*/
|
||||
export const NoDataViewsPrompt = ({
|
||||
onDataViewCreated,
|
||||
onESQLNavigationComplete,
|
||||
allowAdHocDataView = false,
|
||||
onTryESQL: onTryESQLProp,
|
||||
onESQLNavigationComplete,
|
||||
}: NoDataViewsPromptProps) => {
|
||||
const { canCreateNewDataView, openDataViewEditor, dataViewsDocLink, onTryESQL, esqlDocLink } =
|
||||
const { canCreateNewDataView, openDataViewEditor, dataViewsDocLink, esqlDocLink, ...services } =
|
||||
useServices();
|
||||
|
||||
const onTryESQL = onTryESQLProp ?? services.onTryESQL;
|
||||
|
||||
const closeDataViewEditor = useRef<CloseDataViewEditorFn>();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/i18n",
|
||||
"@kbn/shared-ux-utility",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/shared-ux-prompt-no-data-views-types",
|
||||
"@kbn/shared-ux-prompt-no-data-views-mocks",
|
||||
|
|
|
@ -34,17 +34,19 @@ export class StorybookMock extends AbstractStorybookMock<
|
|||
defaultValue: true,
|
||||
},
|
||||
dataViewsDocLink: {
|
||||
options: ['some/link', undefined],
|
||||
control: { type: 'radio' },
|
||||
},
|
||||
esqlDocLink: {
|
||||
options: ['some/link', undefined],
|
||||
options: ['dataviews/link', undefined],
|
||||
control: { type: 'radio' },
|
||||
defaultValue: 'dataviews/link',
|
||||
},
|
||||
canTryEsql: {
|
||||
control: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
esqlDocLink: {
|
||||
options: ['esql/link', undefined],
|
||||
control: { type: 'radio' },
|
||||
defaultValue: 'esql/link',
|
||||
},
|
||||
};
|
||||
dependencies = [];
|
||||
|
||||
|
@ -59,7 +61,7 @@ export class StorybookMock extends AbstractStorybookMock<
|
|||
let onTryESQL;
|
||||
|
||||
if (canTryEsql !== false) {
|
||||
onTryESQL = action('onTryESQL');
|
||||
onTryESQL = action('onTryESQL-from-services');
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -42,7 +42,7 @@ export interface NoDataViewsPromptServices {
|
|||
openDataViewEditor: (options: DataViewEditorOptions) => () => void;
|
||||
/** A link to information about Data Views in Kibana */
|
||||
dataViewsDocLink: string;
|
||||
/** Get a handler for trying ES|QL */
|
||||
/** If the cluster has data, this handler allows the user to try ES|QL */
|
||||
onTryESQL: (() => void) | undefined;
|
||||
/** A link to the documentation for ES|QL */
|
||||
esqlDocLink: string;
|
||||
|
@ -92,7 +92,7 @@ export interface NoDataViewsPromptComponentProps {
|
|||
emptyPromptColor?: EuiEmptyPromptProps['color'];
|
||||
/** Click handler for create button. **/
|
||||
onClickCreate?: () => void;
|
||||
/** Handler for someone wanting to try ES|QL. */
|
||||
/** If the cluster has data, this handler allows the user to try ES|QL */
|
||||
onTryESQL?: () => void;
|
||||
/** Link to documentation on ES|QL. */
|
||||
esqlDocLink?: string;
|
||||
|
@ -104,6 +104,10 @@ export interface NoDataViewsPromptProps {
|
|||
allowAdHocDataView?: boolean;
|
||||
/** Handler for successfully creating a new data view. */
|
||||
onDataViewCreated: (dataView: unknown) => void;
|
||||
/** If the cluster has data, this handler allows the user to try ES|QL */
|
||||
onTryESQL?: () => void;
|
||||
/** Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover. */
|
||||
onESQLNavigationComplete?: () => void;
|
||||
/** Empty prompt color **/
|
||||
emptyPromptColor?: PanelColor;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
],
|
||||
"optionalPlugins": [
|
||||
"noDataPage",
|
||||
"share",
|
||||
"spaces"
|
||||
],
|
||||
"requiredBundles": [
|
||||
|
|
|
@ -26,7 +26,7 @@ import { RouteComponentProps, useLocation, withRouter } from 'react-router-dom';
|
|||
import useObservable from 'react-use/lib/useObservable';
|
||||
|
||||
import { reactRouterNavigate, useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { NoDataViewsPromptComponent } from '@kbn/shared-ux-prompt-no-data-views';
|
||||
import { NoDataViewsPromptComponent, useOnTryESQL } from '@kbn/shared-ux-prompt-no-data-views';
|
||||
import type { SpacesContextProps } from '@kbn/spaces-plugin/public';
|
||||
import { DataViewType } from '@kbn/data-views-plugin/public';
|
||||
import { RollupDeprecationTooltip } from '@kbn/rollup';
|
||||
|
@ -86,6 +86,7 @@ export const IndexPatternTable = ({
|
|||
application,
|
||||
chrome,
|
||||
dataViews,
|
||||
share,
|
||||
IndexPatternEditor,
|
||||
spaces,
|
||||
overlays,
|
||||
|
@ -116,6 +117,12 @@ export const IndexPatternTable = ({
|
|||
const hasDataView = useObservable(dataViewController.hasDataView$, defaults.hasDataView);
|
||||
const hasESData = useObservable(dataViewController.hasESData$, defaults.hasEsData);
|
||||
|
||||
const useOnTryESQLParams = {
|
||||
locatorClient: share?.url.locators,
|
||||
navigateToApp: application.navigateToApp,
|
||||
};
|
||||
const onTryESQL = useOnTryESQL(useOnTryESQLParams);
|
||||
|
||||
const handleOnChange = ({ queryText, error }: { queryText: string; error: unknown }) => {
|
||||
if (!error) {
|
||||
setQuery(queryText);
|
||||
|
@ -370,6 +377,8 @@ export const IndexPatternTable = ({
|
|||
onClickCreate={() => setShowCreateDialog(true)}
|
||||
canCreateNewDataView={application.capabilities.indexPatterns.save as boolean}
|
||||
dataViewsDocLink={docLinks.links.indexPatterns.introduction}
|
||||
onTryESQL={onTryESQL}
|
||||
esqlDocLink={docLinks.links.query.queryESQL}
|
||||
emptyPromptColor={'subdued'}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -17,6 +17,7 @@ import { StartServicesAccessor } from '@kbn/core/public';
|
|||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { ManagementAppMountParams } from '@kbn/management-plugin/public';
|
||||
import { NoDataViewsPromptKibanaProvider } from '@kbn/shared-ux-prompt-no-data-views';
|
||||
import {
|
||||
IndexPatternTableWithRouter,
|
||||
EditIndexPatternContainer,
|
||||
|
@ -64,11 +65,13 @@ export async function mountManagementSection(
|
|||
dataViews,
|
||||
fieldFormats,
|
||||
unifiedSearch,
|
||||
share,
|
||||
spaces,
|
||||
savedObjectsManagement,
|
||||
},
|
||||
indexPatternManagementStart,
|
||||
] = await getStartServices();
|
||||
|
||||
const canSave = dataViews.getCanSaveSync();
|
||||
|
||||
if (!canSave) {
|
||||
|
@ -89,6 +92,7 @@ export async function mountManagementSection(
|
|||
chrome,
|
||||
uiSettings,
|
||||
settings,
|
||||
share,
|
||||
notifications,
|
||||
overlays,
|
||||
unifiedSearch,
|
||||
|
@ -115,23 +119,29 @@ export async function mountManagementSection(
|
|||
ReactDOM.render(
|
||||
<KibanaRenderContextProvider {...startServices}>
|
||||
<KibanaContextProvider services={deps}>
|
||||
<Router history={params.history}>
|
||||
<Routes>
|
||||
<Route path={['/create']}>
|
||||
<IndexPatternTableWithRouter canSave={canSave} showCreateDialog={true} />
|
||||
</Route>
|
||||
<Route path={createEditPath}>
|
||||
<CreateEditFieldContainer />
|
||||
</Route>
|
||||
<Route path={['/dataView/:id']}>
|
||||
<EditIndexPatternContainer />
|
||||
</Route>
|
||||
<Redirect path={'/patterns*'} to={'dataView*'} />
|
||||
<Route path={['/']}>
|
||||
<IndexPatternTableWithRouter canSave={canSave} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
<NoDataViewsPromptKibanaProvider
|
||||
coreStart={{ ...startServices, docLinks, application }}
|
||||
dataViewEditor={dataViewEditor}
|
||||
share={share}
|
||||
>
|
||||
<Router history={params.history}>
|
||||
<Routes>
|
||||
<Route path={['/create']}>
|
||||
<IndexPatternTableWithRouter canSave={canSave} showCreateDialog={true} />
|
||||
</Route>
|
||||
<Route path={createEditPath}>
|
||||
<CreateEditFieldContainer />
|
||||
</Route>
|
||||
<Route path={['/dataView/:id']}>
|
||||
<EditIndexPatternContainer />
|
||||
</Route>
|
||||
<Redirect path={'/patterns*'} to={'dataView*'} />
|
||||
<Route path={['/']}>
|
||||
<IndexPatternTableWithRouter canSave={canSave} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</NoDataViewsPromptKibanaProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaRenderContextProvider>,
|
||||
params.element
|
||||
|
|
|
@ -21,6 +21,7 @@ import { ManagementSetup } from '@kbn/management-plugin/public';
|
|||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
|
||||
export interface IndexPatternManagementSetupDependencies {
|
||||
management: ManagementSetup;
|
||||
|
@ -34,6 +35,7 @@ export interface IndexPatternManagementStartDependencies {
|
|||
dataViewEditor: DataViewEditorStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
share?: SharePluginStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
|
|
|
@ -29,6 +29,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
|||
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
|
||||
import type { NoDataPagePluginSetup } from '@kbn/no-data-page-plugin/public';
|
||||
import { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { IndexPatternManagementStart } from '.';
|
||||
import type { DataViewMgmtService } from './management_app/data_view_management_service';
|
||||
|
||||
|
@ -53,6 +54,7 @@ export interface IndexPatternManagmentContext extends StartServices {
|
|||
fieldFormatEditors: IndexPatternFieldEditorStart['fieldFormatEditors'];
|
||||
IndexPatternEditor: DataViewEditorStart['IndexPatternEditorComponent'];
|
||||
fieldFormats: FieldFormatsStart;
|
||||
share?: SharePluginStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
noDataPage?: NoDataPagePluginSetup;
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"@kbn/code-editor",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/rollup",
|
||||
"@kbn/share-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const esql = getService('esql');
|
||||
const PageObjects = getPageObjects(['discover', 'dashboard']);
|
||||
|
||||
describe('No Data Views: Try ES|QL', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
it('enables user to create a dashboard with ES|QL from no-data-prompt', async () => {
|
||||
await PageObjects.dashboard.navigateToApp();
|
||||
|
||||
await testSubjects.existOrFail('noDataViewsPrompt');
|
||||
await testSubjects.click('tryESQLLink');
|
||||
|
||||
await PageObjects.discover.expectOnDiscover();
|
||||
await esql.expectEsqlStatement('FROM logs* | LIMIT 10');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -37,5 +37,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./dashboard_snapshots'));
|
||||
loadTestFile(require.resolve('./embeddable_library'));
|
||||
loadTestFile(require.resolve('./dashboard_esql_chart'));
|
||||
loadTestFile(require.resolve('./dashboard_esql_no_data'));
|
||||
});
|
||||
}
|
||||
|
|
34
test/functional/apps/management/data_views/_try_esql.ts
Normal file
34
test/functional/apps/management/data_views/_try_esql.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const esql = getService('esql');
|
||||
const PageObjects = getPageObjects(['settings', 'common', 'discover']);
|
||||
|
||||
describe('No Data Views: Try ES|QL', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
it('navigates to Discover and presents an ES|QL query', async () => {
|
||||
await PageObjects.settings.navigateTo();
|
||||
await PageObjects.settings.clickKibanaIndexPatterns();
|
||||
|
||||
await testSubjects.existOrFail('noDataViewsPrompt');
|
||||
await testSubjects.click('tryESQLLink');
|
||||
|
||||
await PageObjects.discover.expectOnDiscover();
|
||||
await esql.expectEsqlStatement('FROM logs* | LIMIT 10');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -38,6 +38,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./data_views/_legacy_url_redirect'));
|
||||
loadTestFile(require.resolve('./data_views/_exclude_index_pattern'));
|
||||
loadTestFile(require.resolve('./data_views/_index_pattern_filter'));
|
||||
loadTestFile(require.resolve('./data_views/_try_esql'));
|
||||
loadTestFile(require.resolve('./data_views/_scripted_fields_filter'));
|
||||
loadTestFile(require.resolve('./_import_objects'));
|
||||
loadTestFile(require.resolve('./data_views/_test_huge_fields'));
|
||||
|
|
|
@ -32,6 +32,12 @@ export class DiscoverPageObject extends FtrService {
|
|||
|
||||
private readonly defaultFindTimeout = this.config.get('timeouts.find');
|
||||
|
||||
/** Ensures that navigation to discover has completed */
|
||||
public async expectOnDiscover() {
|
||||
await this.testSubjects.existOrFail('discoverNewButton');
|
||||
await this.testSubjects.existOrFail('discoverOpenButton');
|
||||
}
|
||||
|
||||
public async getChartTimespan() {
|
||||
return await this.testSubjects.getAttribute('unifiedHistogramChart', 'data-time-range');
|
||||
}
|
||||
|
|
|
@ -7,12 +7,19 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrService } from '../ftr_provider_context';
|
||||
|
||||
export class ESQLService extends FtrService {
|
||||
private readonly retry = this.ctx.getService('retry');
|
||||
private readonly testSubjects = this.ctx.getService('testSubjects');
|
||||
|
||||
/** Ensures that the ES|QL code editor is loaded with a given statement */
|
||||
public async expectEsqlStatement(statement: string) {
|
||||
const codeEditor = await this.testSubjects.find('ESQLEditor');
|
||||
expect(await codeEditor.getAttribute('innerText')).to.contain(statement);
|
||||
}
|
||||
|
||||
public async getHistoryItems(): Promise<string[][]> {
|
||||
const queryHistory = await this.testSubjects.find('ESQLEditor-queryHistory');
|
||||
const tableBody = await this.retry.try(async () => queryHistory.findByTagName('tbody'));
|
||||
|
|
|
@ -7268,9 +7268,6 @@
|
|||
"sharedUXPackages.fileUpload.uploadCompleteButtonLabel": "Chargement terminé",
|
||||
"sharedUXPackages.fileUpload.uploadDoneToolTipContent": "Votre fichier a bien été chargé !",
|
||||
"sharedUXPackages.fileUpload.uploadingButtonLabel": "Chargement",
|
||||
"sharedUXPackages.no_data_views.esqlButtonLabel": "Langue : ES|QL",
|
||||
"sharedUXPackages.no_data_views.esqlDocsLink": "En savoir plus.",
|
||||
"sharedUXPackages.no_data_views.esqlMessage": "Vous pouvez aussi rechercher vos données en utilisant directement ES|QL. {docsLink}",
|
||||
"sharedUXPackages.noDataConfig.addIntegrationsDescription": "Utilisez Elastic Agent pour collecter des données et créer des solutions Analytics.",
|
||||
"sharedUXPackages.noDataConfig.addIntegrationsTitle": "Ajouter des intégrations",
|
||||
"sharedUXPackages.noDataConfig.analytics": "Analyse",
|
||||
|
@ -7292,8 +7289,6 @@
|
|||
"sharedUXPackages.noDataViewsPrompt.dataViewExplanation": "Les vues de données identifient les données Elasticsearch que vous souhaitez explorer. Vous pouvez faire pointer des vues de données vers un ou plusieurs flux de données, index et alias d'index, tels que vos données de log d'hier, ou vers tous les index contenant vos données de log.",
|
||||
"sharedUXPackages.noDataViewsPrompt.learnMore": "Envie d'en savoir plus ?",
|
||||
"sharedUXPackages.noDataViewsPrompt.noPermission.dataViewExplanation": "Les vues de données identifient les données Elasticsearch que vous souhaitez explorer. Pour créer des vues de données, demandez les autorisations requises à votre administrateur.",
|
||||
"sharedUXPackages.noDataViewsPrompt.noPermission.title": "Vous devez disposer d'une autorisation pour pouvoir créer des vues de données",
|
||||
"sharedUXPackages.noDataViewsPrompt.nowCreate": "Créez à présent une vue de données.",
|
||||
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "Lisez les documents",
|
||||
"sharedUXPackages.noDataViewsPrompt.youHaveData": "Vous avez des données dans Elasticsearch.",
|
||||
"sharedUXPackages.prompt.errors.notFound.body": "Désolé, la page que vous recherchez est introuvable. Elle a peut-être été retirée ou renommée, ou peut-être qu'elle n'a jamais existé.",
|
||||
|
|
|
@ -7022,9 +7022,6 @@
|
|||
"sharedUXPackages.fileUpload.uploadCompleteButtonLabel": "アップロード完了",
|
||||
"sharedUXPackages.fileUpload.uploadDoneToolTipContent": "ファイルは正常にアップロードされました。",
|
||||
"sharedUXPackages.fileUpload.uploadingButtonLabel": "アップロード中",
|
||||
"sharedUXPackages.no_data_views.esqlButtonLabel": "言語:ES|QL",
|
||||
"sharedUXPackages.no_data_views.esqlDocsLink": "詳細情報",
|
||||
"sharedUXPackages.no_data_views.esqlMessage": "あるいは、直接ES|QLを使用してデータをクエリできます。{docsLink}",
|
||||
"sharedUXPackages.noDataConfig.addIntegrationsDescription": "Elasticエージェントを使用して、データを収集し、分析ソリューションを構築します。",
|
||||
"sharedUXPackages.noDataConfig.addIntegrationsTitle": "統合の追加",
|
||||
"sharedUXPackages.noDataConfig.analytics": "分析",
|
||||
|
@ -7046,8 +7043,6 @@
|
|||
"sharedUXPackages.noDataViewsPrompt.dataViewExplanation": "データビューは、探索するElasticsearchデータを特定します。昨日からのログデータ、ログデータを含むすべてのインデックスなど、1つ以上のデータストリーム、インデックス、インデックスエイリアスをデータビューで参照できます。",
|
||||
"sharedUXPackages.noDataViewsPrompt.learnMore": "詳細について",
|
||||
"sharedUXPackages.noDataViewsPrompt.noPermission.dataViewExplanation": "データビューは、探索するElasticsearchデータを特定します。データビューを作成するには、必要な権限を管理者に依頼してください。",
|
||||
"sharedUXPackages.noDataViewsPrompt.noPermission.title": "データビューを作成するための権限が必要です。",
|
||||
"sharedUXPackages.noDataViewsPrompt.nowCreate": "ここでデータビューを作成します。",
|
||||
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "<b>ドキュメント</b><b>を読む</b>",
|
||||
"sharedUXPackages.noDataViewsPrompt.youHaveData": "Elasticsearchにデータがあります。",
|
||||
"sharedUXPackages.prompt.errors.notFound.body": "申し訳ございません。お探しのページは見つかりませんでした。削除または名前変更されたか、そもそも存在していなかった可能性があります。",
|
||||
|
|
|
@ -7037,9 +7037,6 @@
|
|||
"sharedUXPackages.fileUpload.uploadCompleteButtonLabel": "上传完成",
|
||||
"sharedUXPackages.fileUpload.uploadDoneToolTipContent": "您的文件已成功上传!",
|
||||
"sharedUXPackages.fileUpload.uploadingButtonLabel": "正在上传",
|
||||
"sharedUXPackages.no_data_views.esqlButtonLabel": "语言:ES|QL",
|
||||
"sharedUXPackages.no_data_views.esqlDocsLink": "了解详情。",
|
||||
"sharedUXPackages.no_data_views.esqlMessage": "或者,您可以直接使用 ES|QL 查询数据。{docsLink}",
|
||||
"sharedUXPackages.noDataConfig.addIntegrationsDescription": "使用 Elastic 代理收集数据并增建分析解决方案。",
|
||||
"sharedUXPackages.noDataConfig.addIntegrationsTitle": "添加集成",
|
||||
"sharedUXPackages.noDataConfig.analytics": "分析",
|
||||
|
@ -7061,8 +7058,6 @@
|
|||
"sharedUXPackages.noDataViewsPrompt.dataViewExplanation": "数据视图标识您要浏览的 Elasticsearch 数据。您可以将数据视图指向一个或多个数据流、索引和索引别名(例如昨天的日志数据),或包含日志数据的所有索引。",
|
||||
"sharedUXPackages.noDataViewsPrompt.learnMore": "希望了解详情?",
|
||||
"sharedUXPackages.noDataViewsPrompt.noPermission.dataViewExplanation": "数据视图标识您要浏览的 Elasticsearch 数据。要创建数据视图,请联系管理员获得所需权限。",
|
||||
"sharedUXPackages.noDataViewsPrompt.noPermission.title": "您需要权限以创建数据视图",
|
||||
"sharedUXPackages.noDataViewsPrompt.nowCreate": "现在,创建数据视图。",
|
||||
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "阅读文档",
|
||||
"sharedUXPackages.noDataViewsPrompt.youHaveData": "您在 Elasticsearch 中有数据。",
|
||||
"sharedUXPackages.prompt.errors.notFound.body": "抱歉,找不到您要查找的页面。该页面可能已移除、重命名,或可能根本不存在。",
|
||||
|
|
|
@ -131,10 +131,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
expect(navLinks).to.eql(['Stack Management']);
|
||||
});
|
||||
|
||||
it(`index pattern listing doesn't show create button`, async () => {
|
||||
it(`index pattern listing shows disabled create button`, async () => {
|
||||
await settings.clickKibanaIndexPatterns();
|
||||
await testSubjects.existOrFail('noDataViewsPrompt');
|
||||
await testSubjects.missingOrFail('createDataViewButton');
|
||||
const createDataViewButton = await testSubjects.find('createDataViewButton');
|
||||
const isDisabled = await createDataViewButton.getAttribute('disabled');
|
||||
expect(isDisabled).to.be('true');
|
||||
});
|
||||
|
||||
it(`shows read-only badge`, async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue