[Serverless Elasticsearch] Fix user is blocked from moving forward when opening Discover, Dashboard, or Visualize Library

This commit is contained in:
Anton Dosov 2023-08-28 16:24:34 +02:00 committed by GitHub
parent 84b683bd7a
commit 243142d9c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 496 additions and 56 deletions

1
.github/CODEOWNERS vendored
View file

@ -517,6 +517,7 @@ x-pack/plugins/monitoring @elastic/infra-monitoring-ui
src/plugins/navigation @elastic/appex-sharedux
src/plugins/newsfeed @elastic/kibana-core
test/common/plugins/newsfeed @elastic/kibana-core
src/plugins/no_data_page @elastic/appex-sharedux
x-pack/plugins/notifications @elastic/appex-sharedux
packages/kbn-object-versioning @elastic/appex-sharedux
x-pack/plugins/observability_ai_assistant @elastic/obs-ai-assistant

View file

@ -32,3 +32,6 @@ telemetry.labels.serverless: search
# Alerts config
xpack.actions.enabledActionTypes: ['.email', '.index', '.slack', '.jira', '.webhook', '.teams']
# Customize empty page state for analytics apps
no_data_page.analyticsNoDataPageFlavor: 'serverless_search'

View file

@ -254,6 +254,10 @@ It also provides a stateful version of it on the start contract.
Content is fetched from the remote (https://feeds.elastic.co) once a day, with periodic checks if the content needs to be refreshed. All newsfeed content is hosted remotely.
|{kib-repo}blob/{branch}/src/plugins/no_data_page/README.md[noDataPage]
|Helps to globally configure the no data page components
|{kib-repo}blob/{branch}/src/plugins/presentation_util/README.mdx[presentationUtil]
|The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas).

View file

@ -535,6 +535,7 @@
"@kbn/navigation-plugin": "link:src/plugins/navigation",
"@kbn/newsfeed-plugin": "link:src/plugins/newsfeed",
"@kbn/newsfeed-test-plugin": "link:test/common/plugins/newsfeed",
"@kbn/no-data-page-plugin": "link:src/plugins/no_data_page",
"@kbn/notifications-plugin": "link:x-pack/plugins/notifications",
"@kbn/object-versioning": "link:packages/kbn-object-versioning",
"@kbn/observability-ai-assistant-plugin": "link:x-pack/plugins/observability_ai_assistant",

View file

@ -96,6 +96,7 @@ pageLoadAssetSize:
monitoring: 80000
navigation: 37269
newsfeed: 42228
noDataPage: 5000
observability: 115443
observabilityAIAssistant: 25000
observabilityOnboarding: 19573

View file

@ -35,7 +35,9 @@ export const NoDataCard = ({ href: srcHref, category, description, ...props }: P
return (
<RedirectAppLinksContainer>
<Component {...{ ...props, href, canAccessFleet, description }} />
<Component
{...{ ...props, href, canAccessFleet: props.canAccessFleet ?? canAccessFleet, description }}
/>
</RedirectAppLinksContainer>
);
};

View file

@ -79,4 +79,4 @@ export type NoDataCardComponentProps = Partial<
/**
* Props for the `NoDataCard` sevice-connected component.
*/
export type NoDataCardProps = Omit<NoDataCardComponentProps, 'canAccessFleet'>;
export type NoDataCardProps = NoDataCardComponentProps;

View file

@ -10,7 +10,10 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaNoDataPage } from '@kbn/shared-ux-page-kibana-no-data';
import { render, screen } from '@testing-library/react';
import { AnalyticsNoDataPage } from './analytics_no_data_page.component';
import { AnalyticsNoDataPageProvider } from './services';
@ -28,6 +31,7 @@ describe('AnalyticsNoDataPageComponent', () => {
onDataViewCreated={onDataViewCreated}
kibanaGuideDocLink={'http://www.test.com'}
showPlainSpinner={false}
prependBasePath={(path: string) => path}
/>
</AnalyticsNoDataPageProvider>
);
@ -52,6 +56,7 @@ describe('AnalyticsNoDataPageComponent', () => {
kibanaGuideDocLink={'http://www.test.com'}
allowAdHocDataView={true}
showPlainSpinner={false}
prependBasePath={(path: string) => path}
/>
</AnalyticsNoDataPageProvider>
);
@ -61,4 +66,86 @@ describe('AnalyticsNoDataPageComponent', () => {
expect(component.find(KibanaNoDataPage).length).toBe(1);
expect(component.find(KibanaNoDataPage).props().allowAdHocDataView).toBe(true);
});
describe('no data state', () => {
describe('kibana flavor', () => {
it('renders add integrations card', async () => {
render(
<I18nProvider>
<AnalyticsNoDataPageProvider {...{ ...services, hasESData: async () => false }}>
<AnalyticsNoDataPage
onDataViewCreated={onDataViewCreated}
kibanaGuideDocLink={'http://www.test.com'}
showPlainSpinner={false}
prependBasePath={(path: string) => path}
/>
</AnalyticsNoDataPageProvider>
</I18nProvider>
);
await screen.findByTestId('kbnOverviewAddIntegrations');
await screen.getAllByText('Add integrations');
});
it('renders disabled add integrations card when fleet is not available', async () => {
render(
<I18nProvider>
<AnalyticsNoDataPageProvider
{...{ ...services, hasESData: async () => false, canAccessFleet: false }}
>
<AnalyticsNoDataPage
onDataViewCreated={onDataViewCreated}
kibanaGuideDocLink={'http://www.test.com'}
showPlainSpinner={false}
prependBasePath={(path: string) => path}
/>
</AnalyticsNoDataPageProvider>
</I18nProvider>
);
await screen.findByTestId('kbnOverviewAddIntegrations');
await screen.getByText('Contact your administrator');
});
});
describe('serverless_search flavor', () => {
it('renders getting started card', async () => {
render(
<I18nProvider>
<AnalyticsNoDataPageProvider {...{ ...services, hasESData: async () => false }}>
<AnalyticsNoDataPage
pageFlavor={'serverless_search'}
onDataViewCreated={onDataViewCreated}
kibanaGuideDocLink={'http://www.test.com'}
showPlainSpinner={false}
prependBasePath={(path: string) => path}
/>
</AnalyticsNoDataPageProvider>
</I18nProvider>
);
await screen.findByTestId('kbnOverviewElasticsearchGettingStarted');
});
it('renders the same getting started card when fleet is not available', async () => {
render(
<I18nProvider>
<AnalyticsNoDataPageProvider
{...{ ...services, hasESData: async () => false, canAccessFleet: false }}
>
<AnalyticsNoDataPage
onDataViewCreated={onDataViewCreated}
kibanaGuideDocLink={'http://www.test.com'}
showPlainSpinner={false}
prependBasePath={(path: string) => path}
pageFlavor={'serverless_search'}
/>
</AnalyticsNoDataPageProvider>
</I18nProvider>
);
await screen.findByTestId('kbnOverviewElasticsearchGettingStarted');
});
});
});
});

View file

@ -8,6 +8,8 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { KibanaNoDataPage } from '@kbn/shared-ux-page-kibana-no-data';
import { KibanaNoDataPageProps } from '@kbn/shared-ux-page-kibana-no-data-types';
import { AnalyticsNoDataPageFlavor } from '@kbn/shared-ux-page-analytics-no-data-types';
/**
* Props for the pure component.
@ -21,26 +23,63 @@ export interface Props {
allowAdHocDataView?: boolean;
/** if the kibana instance is customly branded */
showPlainSpinner: boolean;
/** The flavor of the empty page to use. */
pageFlavor?: AnalyticsNoDataPageFlavor;
prependBasePath: (path: string) => string;
}
const solution = i18n.translate('sharedUXPackages.noDataConfig.analytics', {
defaultMessage: 'Analytics',
});
const pageTitle = i18n.translate('sharedUXPackages.noDataConfig.analyticsPageTitle', {
defaultMessage: 'Welcome to Analytics!',
});
const addIntegrationsTitle = i18n.translate('sharedUXPackages.noDataConfig.addIntegrationsTitle', {
defaultMessage: 'Add integrations',
});
const addIntegrationsDescription = i18n.translate(
'sharedUXPackages.noDataConfig.addIntegrationsDescription',
{
defaultMessage: 'Use Elastic Agent to collect data and build out Analytics solutions.',
}
);
const flavors: {
[K in AnalyticsNoDataPageFlavor]: (deps: {
kibanaGuideDocLink: string;
prependBasePath: (path: string) => string;
}) => KibanaNoDataPageProps['noDataConfig'];
} = {
kibana: ({ kibanaGuideDocLink }) => ({
solution: i18n.translate('sharedUXPackages.noDataConfig.analytics', {
defaultMessage: 'Analytics',
}),
pageTitle: i18n.translate('sharedUXPackages.noDataConfig.analyticsPageTitle', {
defaultMessage: 'Welcome to Analytics!',
}),
logo: 'logoKibana',
action: {
elasticAgent: {
title: i18n.translate('sharedUXPackages.noDataConfig.addIntegrationsTitle', {
defaultMessage: 'Add integrations',
}),
description: i18n.translate('sharedUXPackages.noDataConfig.addIntegrationsDescription', {
defaultMessage: 'Use Elastic Agent to collect data and build out Analytics solutions.',
}),
'data-test-subj': 'kbnOverviewAddIntegrations',
},
},
docsLink: kibanaGuideDocLink,
}),
serverless_search: ({ prependBasePath }) => ({
solution: i18n.translate('sharedUXPackages.noDataConfig.elasticsearch', {
defaultMessage: 'Elasticsearch',
}),
pageTitle: i18n.translate('sharedUXPackages.noDataConfig.elasticsearchPageTitle', {
defaultMessage: 'Welcome to Elasticsearch!',
}),
logo: 'logoElasticsearch',
action: {
elasticsearch: {
title: i18n.translate('sharedUXPackages.noDataConfig.elasticsearchTitle', {
defaultMessage: 'Get started',
}),
description: i18n.translate('sharedUXPackages.noDataConfig.elasticsearchDescription', {
defaultMessage:
'Set up your programming language client, ingest some data, and start searching.',
}),
'data-test-subj': 'kbnOverviewElasticsearchGettingStarted',
href: prependBasePath('/app/elasticsearch/'),
/** force the no data card to be shown **/
canAccessFleet: true,
},
},
}),
};
/**
* A pure component of an entire page that can be displayed when Kibana "has no data", specifically for Analytics.
@ -50,20 +89,13 @@ export const AnalyticsNoDataPage = ({
onDataViewCreated,
allowAdHocDataView,
showPlainSpinner,
prependBasePath,
pageFlavor = 'kibana',
}: Props) => {
const noDataConfig = {
solution,
pageTitle,
logo: 'logoKibana',
action: {
elasticAgent: {
title: addIntegrationsTitle,
description: addIntegrationsDescription,
'data-test-subj': 'kbnOverviewAddIntegrations',
},
},
docsLink: kibanaGuideDocLink,
};
const noDataConfig: KibanaNoDataPageProps['noDataConfig'] = flavors[pageFlavor]({
kibanaGuideDocLink,
prependBasePath,
});
return (
<KibanaNoDataPage

View file

@ -41,6 +41,8 @@ describe('AnalyticsNoDataPage', () => {
expect(component.find(Component).props().kibanaGuideDocLink).toBe(services.kibanaGuideDocLink);
expect(component.find(Component).props().onDataViewCreated).toBe(onDataViewCreated);
expect(component.find(Component).props().allowAdHocDataView).toBe(true);
expect(component.find(Component).props().prependBasePath).toBe(services.prependBasePath);
expect(component.find(Component).props().pageFlavor).toBe(services.pageFlavor);
});
it('passes correct boolean value to showPlainSpinner', () => {

View file

@ -21,7 +21,7 @@ export const AnalyticsNoDataPage = ({
allowAdHocDataView,
}: AnalyticsNoDataPageProps) => {
const services = useServices();
const { kibanaGuideDocLink, customBranding } = services;
const { kibanaGuideDocLink, customBranding, prependBasePath, pageFlavor } = services;
const { hasCustomBranding$ } = customBranding;
const showPlainSpinner = useObservable(hasCustomBranding$) ?? false;
@ -32,6 +32,8 @@ export const AnalyticsNoDataPage = ({
allowAdHocDataView,
kibanaGuideDocLink,
showPlainSpinner,
prependBasePath,
pageFlavor,
}}
/>
);

View file

@ -27,10 +27,10 @@ export const AnalyticsNoDataPageProvider: FC<AnalyticsNoDataPageServices> = ({
children,
...services
}) => {
const { kibanaGuideDocLink, customBranding } = services;
const { kibanaGuideDocLink, customBranding, prependBasePath, pageFlavor } = services;
return (
<Context.Provider value={{ kibanaGuideDocLink, customBranding }}>
<Context.Provider value={{ kibanaGuideDocLink, customBranding, prependBasePath, pageFlavor }}>
<KibanaNoDataPageProvider {...services}>{children}</KibanaNoDataPageProvider>
</Context.Provider>
);
@ -48,6 +48,8 @@ export const AnalyticsNoDataPageKibanaProvider: FC<AnalyticsNoDataPageKibanaDepe
customBranding: {
hasCustomBranding$: dependencies.coreStart.customBranding.hasCustomBranding$,
},
prependBasePath: dependencies.coreStart.http.basePath.prepend,
pageFlavor: dependencies.noDataPage?.getAnalyticsNoDataPageFlavor() ?? 'kibana',
};
return (
<Context.Provider {...{ value }}>

View file

@ -19,6 +19,8 @@
"@kbn/shared-ux-page-analytics-no-data-types",
"@kbn/test-jest-helpers",
"@kbn/shared-ux-page-analytics-no-data-mocks",
"@kbn/shared-ux-page-kibana-no-data-types",
"@kbn/i18n-react",
],
"exclude": [
"target/**/*",

View file

@ -15,6 +15,8 @@ export const getServicesMock = () => {
...getKibanaNoDataPageServicesMock(),
kibanaGuideDocLink: 'Kibana guide',
customBranding: { hasCustomBranding$: of(false) },
prependBasePath: (path) => path,
pageFlavor: 'kibana',
};
return services;
@ -26,6 +28,8 @@ export const getServicesMockCustomBranding = () => {
// this mock will have custom branding set to true
customBranding: { hasCustomBranding$: of(true) },
kibanaGuideDocLink: 'Kibana guide',
prependBasePath: (path) => path,
pageFlavor: 'kibana',
};
return services;

View file

@ -51,6 +51,8 @@ export class StorybookMock extends AbstractStorybookMock<
customBranding: {
hasCustomBranding$: of(false),
},
pageFlavor: 'kibana',
prependBasePath: (path) => path,
...kibanaNoDataMock.getServices(params),
};
}

View file

@ -17,6 +17,8 @@ import { Observable } from 'rxjs';
export interface Services {
kibanaGuideDocLink: string;
customBranding: { hasCustomBranding$: Observable<boolean> };
prependBasePath: (path: string) => string;
pageFlavor: AnalyticsNoDataPageFlavor;
}
/**
@ -24,6 +26,8 @@ export interface Services {
*/
export type AnalyticsNoDataPageServices = Services & KibanaNoDataPageServices;
export type AnalyticsNoDataPageFlavor = 'kibana' | 'serverless_search';
export interface KibanaDependencies {
coreStart: {
docLinks: {
@ -36,6 +40,14 @@ export interface KibanaDependencies {
customBranding: {
hasCustomBranding$: Observable<boolean>;
};
http: {
basePath: {
prepend: (path: string) => string;
};
};
};
noDataPage?: {
getAnalyticsNoDataPageFlavor: () => AnalyticsNoDataPageFlavor;
};
}

View file

@ -33,13 +33,13 @@ export const NoDataPage = ({
values: { solution },
});
const link = (
const link = docsLink ? (
<EuiLink href={docsLink} target="_blank">
<FormattedMessage id="sharedUXPackages.noDataPage.intro.link" defaultMessage="learn more" />
</EuiLink>
);
) : null;
const message = (
const message = link ? (
<FormattedMessage
id="sharedUXPackages.noDataPage.intro"
defaultMessage="Add your data to get started, or {link} about {solution}."
@ -48,6 +48,11 @@ export const NoDataPage = ({
link,
}}
/>
) : (
<FormattedMessage
id="sharedUXPackages.noDataPage.introNoDocLink"
defaultMessage="Add your data to get started."
/>
);
return (

View file

@ -31,9 +31,9 @@ export interface NoDataPageProps extends CommonProps, ActionCardProps {
*/
solution: string;
/**
* Required to set the docs link for the whole solution
* Required in "kibana" flavor to set the docs link for the whole solution, otherwise optional
*/
docsLink: string;
docsLink?: string;
/**
* Optionally replace the auto-generated logo
*/

View file

@ -34,7 +34,8 @@
"screenshotMode",
"usageCollection",
"taskManager",
"serverless"
"serverless",
"noDataPage"
],
"requiredBundles": ["kibanaReact", "kibanaUtils", "presentationUtil"]
}

View file

@ -26,6 +26,7 @@ export const DashboardAppNoDataPage = ({
http: { basePath },
documentationLinks: { indexPatternsDocLink, kibanaGuideDocLink },
customBranding,
noDataPage,
} = pluginServices.getServices();
const analyticsServices = {
@ -44,6 +45,7 @@ export const DashboardAppNoDataPage = ({
},
dataViews,
dataViewEditor,
noDataPage,
};
return (
<AnalyticsNoDataPageKibanaProvider {...analyticsServices}>

View file

@ -52,6 +52,7 @@ import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plu
import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
import { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
@ -108,6 +109,7 @@ export interface DashboardStartDependencies {
visualizations: VisualizationsStart;
customBranding: CustomBrandingStart;
serverless?: ServerlessPluginStart;
noDataPage?: NoDataPagePluginStart;
}
export interface DashboardSetup {

View file

@ -0,0 +1,16 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { NoDataPageService } from './types';
export type NoDataPageServiceFactory = PluginServiceFactory<NoDataPageService>;
export const noDataPageServiceFactory: NoDataPageServiceFactory = () => {
return { getAnalyticsNoDataPageFlavor: () => 'kibana' };
};

View file

@ -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 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 or the Server
* Side Public License, v 1.
*/
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { DashboardStartDependencies } from '../../plugin';
import { NoDataPageService } from './types';
export type NoDataPageServiceFactory = KibanaPluginServiceFactory<
NoDataPageService,
DashboardStartDependencies
>;
export const noDataPageServiceFactory: NoDataPageServiceFactory = ({ startPlugins }) => {
const { noDataPage } = startPlugins;
return {
getAnalyticsNoDataPageFlavor: noDataPage?.getAnalyticsNoDataPageFlavor ?? (() => 'kibana'),
};
};

View file

@ -0,0 +1,13 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
export interface NoDataPageService {
getAnalyticsNoDataPageFlavor: NoDataPagePluginStart['getAnalyticsNoDataPageFlavor'];
}

View file

@ -42,6 +42,7 @@ import { customBrandingServiceFactory } from './custom_branding/custom_branding.
import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service.stub';
import { contentManagementServiceFactory } from './content_management/content_management_service.stub';
import { serverlessServiceFactory } from './serverless/serverless_service.stub';
import { noDataPageServiceFactory } from './no_data_page/no_data_page_service.stub';
export const providers: PluginServiceProviders<DashboardServices> = {
dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory),
@ -72,6 +73,7 @@ export const providers: PluginServiceProviders<DashboardServices> = {
savedObjectsManagement: new PluginServiceProvider(savedObjectsManagementServiceFactory),
contentManagement: new PluginServiceProvider(contentManagementServiceFactory),
serverless: new PluginServiceProvider(serverlessServiceFactory),
noDataPage: new PluginServiceProvider(noDataPageServiceFactory),
};
export const registry = new PluginServiceRegistry<DashboardServices>(providers);

View file

@ -43,6 +43,7 @@ import { savedObjectsManagementServiceFactory } from './saved_objects_management
import { dashboardContentManagementServiceFactory } from './dashboard_content_management/dashboard_content_management_service';
import { contentManagementServiceFactory } from './content_management/content_management_service';
import { serverlessServiceFactory } from './serverless/serverless_service';
import { noDataPageServiceFactory } from './no_data_page/no_data_page_service';
const providers: PluginServiceProviders<DashboardServices, DashboardPluginServiceParams> = {
dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory, [
@ -86,6 +87,7 @@ const providers: PluginServiceProviders<DashboardServices, DashboardPluginServic
savedObjectsManagement: new PluginServiceProvider(savedObjectsManagementServiceFactory),
contentManagement: new PluginServiceProvider(contentManagementServiceFactory),
serverless: new PluginServiceProvider(serverlessServiceFactory),
noDataPage: new PluginServiceProvider(noDataPageServiceFactory),
};
export const pluginServices = new PluginServices<DashboardServices>();

View file

@ -38,6 +38,7 @@ import { DashboardUrlForwardingService } from './url_forwarding/types';
import { DashboardUsageCollectionService } from './usage_collection/types';
import { DashboardVisualizationsService } from './visualizations/types';
import { DashboardServerlessService } from './serverless/types';
import { NoDataPageService } from './no_data_page/types';
export type DashboardPluginServiceParams = KibanaPluginServiceParams<DashboardStartDependencies> & {
initContext: PluginInitializerContext; // need a custom type so that initContext is a required parameter for initializerContext
@ -72,4 +73,5 @@ export interface DashboardServices {
savedObjectsManagement: SavedObjectsManagementPluginStart;
contentManagement: ContentManagementPublicStart;
serverless: DashboardServerlessService; // TODO: make this optional in follow up
noDataPage: NoDataPageService;
}

View file

@ -64,7 +64,8 @@
"@kbn/content-management-table-list-view-table",
"@kbn/shared-ux-prompt-not-found",
"@kbn/content-management-content-editor",
"@kbn/serverless"
"@kbn/serverless",
"@kbn/no-data-page-plugin"
],
"exclude": ["target/**/*"]
}

View file

@ -26,7 +26,7 @@
"expressions",
"unifiedSearch",
"unifiedHistogram",
"contentManagement",
"contentManagement"
],
"optionalPlugins": [
"home",
@ -36,7 +36,8 @@
"triggersActionsUi",
"savedObjectsTaggingOss",
"lens",
"serverless"
"serverless",
"noDataPage"
],
"requiredBundles": ["kibanaUtils", "kibanaReact", "unifiedSearch"],
"extraPublicDirs": ["common"]

View file

@ -48,11 +48,7 @@ export interface MainRouteProps {
mode?: DiscoverDisplayMode;
}
export function DiscoverMainRoute({
customizationCallbacks,
isDev,
mode = 'standalone',
}: MainRouteProps) {
export function DiscoverMainRoute({ customizationCallbacks, mode = 'standalone' }: MainRouteProps) {
const history = useHistory();
const services = useDiscoverServices();
const {
@ -109,7 +105,7 @@ export function DiscoverMainRoute({
const hasUserDataViewValue = await data.dataViews.hasData
.hasUserDataView()
.catch(() => false);
const hasESDataValue = isDev || (await data.dataViews.hasData.hasESData().catch(() => false));
const hasESDataValue = await data.dataViews.hasData.hasESData().catch(() => false);
setHasUserDataView(hasUserDataViewValue);
setHasESData(hasESDataValue);
@ -134,7 +130,7 @@ export function DiscoverMainRoute({
setError(e);
return false;
}
}, [data.dataViews, isDev, savedSearchId]);
}, [data.dataViews, savedSearchId]);
const loadSavedSearch = useCallback(
async (nextDataView?: DataView) => {
@ -256,11 +252,12 @@ export function DiscoverMainRoute({
// We've already called this, so we can optimize the analytics services to
// use the already-retrieved data to avoid a double-call.
hasESData: () => Promise.resolve(isDev ? true : hasESData),
hasESData: () => Promise.resolve(hasESData),
hasUserDataView: () => Promise.resolve(hasUserDataView),
},
},
dataViewEditor,
noDataPage: services.noDataPage,
};
return (

View file

@ -53,6 +53,7 @@ import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
import type { ContentClient } from '@kbn/content-management-plugin/public';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
import { getHistory } from './kibana_services';
import { DiscoverStartPlugins } from './plugin';
import { DiscoverContextAppLocator } from './application/context/services/locator';
@ -111,6 +112,7 @@ export interface DiscoverServices {
uiActions: UiActionsStart;
contentClient: ContentClient;
serverless?: ServerlessPluginStart;
noDataPage?: NoDataPagePluginStart;
}
export const buildServices = memoize(function (
@ -171,5 +173,6 @@ export const buildServices = memoize(function (
uiActions: plugins.uiActions,
contentClient: plugins.contentManagement.client,
serverless: plugins.serverless,
noDataPage: plugins.noDataPage,
};
});

View file

@ -46,6 +46,7 @@ import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
import { DOC_TABLE_LEGACY, TRUNCATE_MAX_HEIGHT } from '@kbn/discover-utils';
import { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
import { PLUGIN_ID } from '../common';
import { DocViewInput, DocViewInputFn } from './services/doc_views/doc_views_types';
import { DocViewsRegistry } from './services/doc_views/doc_views_registry';
@ -213,6 +214,7 @@ export interface DiscoverStartPlugins {
lens: LensPublicStart;
contentManagement: ContentManagementPublicStart;
serverless?: ServerlessPluginStart;
noDataPage?: NoDataPagePluginStart;
}
/**

View file

@ -70,7 +70,8 @@
"@kbn/content-management-plugin",
"@kbn/serverless",
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-render"
"@kbn/react-kibana-context-render",
"@kbn/no-data-page-plugin"
],
"exclude": [
"target/**/*"

View file

@ -0,0 +1,3 @@
# No Data Page
Helps to globally configure the no data page components

View file

@ -0,0 +1,19 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { schema, TypeOf, offeringBasedSchema } from '@kbn/config-schema';
export const configSchema = schema.object({
analyticsNoDataPageFlavor: offeringBasedSchema({
serverless: schema.oneOf(
[schema.oneOf([schema.literal('kibana'), schema.literal('serverless_search')])],
{ defaultValue: 'kibana' as const }
),
}),
});
export type NoDataPageConfig = TypeOf<typeof configSchema>;

View file

@ -0,0 +1,16 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/no_data_page'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/no_data_page',
coverageReporters: ['text', 'html'],
collectCoverageFrom: ['<rootDir>/src/plugins/no_data_page/{common,public,server}/**/*.{ts,tsx}'],
};

View file

@ -0,0 +1,10 @@
{
"type": "plugin",
"id": "@kbn/no-data-page-plugin",
"owner": "@elastic/appex-sharedux",
"plugin": {
"id": "noDataPage",
"server": true,
"browser": true
}
}

View file

@ -0,0 +1,16 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { PluginInitializerContext } from '@kbn/core-plugins-browser';
import { NoDataPagePlugin } from './plugin';
export function plugin(ctx: PluginInitializerContext) {
return new NoDataPagePlugin(ctx);
}
export type { NoDataPagePluginSetup, NoDataPagePluginStart } from './types';

View file

@ -0,0 +1,31 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import type { NoDataPagePluginSetup, NoDataPagePluginStart } from './types';
import type { NoDataPageConfig } from '../config';
export class NoDataPagePlugin implements Plugin<NoDataPagePluginSetup> {
constructor(private initializerContext: PluginInitializerContext<NoDataPageConfig>) {}
public setup(core: CoreSetup): NoDataPagePluginSetup {
return {
getAnalyticsNoDataPageFlavor: () => {
return this.initializerContext.config.get().analyticsNoDataPageFlavor;
},
};
}
public start(core: CoreStart): NoDataPagePluginStart {
return {
getAnalyticsNoDataPageFlavor: () => {
return this.initializerContext.config.get().analyticsNoDataPageFlavor;
},
};
}
}

View file

@ -0,0 +1,13 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
export interface NoDataPagePluginSetup {
getAnalyticsNoDataPageFlavor: () => 'kibana' | 'serverless_search';
}
export type NoDataPagePluginStart = NoDataPagePluginSetup;

View file

@ -0,0 +1,25 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { PluginConfigDescriptor } from '@kbn/core-plugins-server';
import { configSchema, NoDataPageConfig } from '../config';
export const config: PluginConfigDescriptor<NoDataPageConfig> = {
exposeToBrowser: {
analyticsNoDataPageFlavor: true,
},
schema: configSchema,
};
export function plugin() {
return new (class NoDataPagePlugin {
setup() {}
start() {}
})();
}

View file

@ -0,0 +1,14 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
"include": ["common/**/*", "public/**/*", "server/**/*", "config.ts"],
"kbn_references": [
"@kbn/core",
"@kbn/core-plugins-browser",
"@kbn/core-plugins-server",
"@kbn/config-schema",
],
"exclude": ["target/**/*"]
}

View file

@ -34,7 +34,8 @@
"share",
"spaces",
"savedObjectsTaggingOss",
"serverless"
"serverless",
"noDataPage"
],
"requiredBundles": [
"kibanaUtils",

View file

@ -64,6 +64,7 @@ import {
ContentManagementPublicSetup,
ContentManagementPublicStart,
} from '@kbn/content-management-plugin/public';
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
import type { TypesSetup, TypesStart } from './vis_types';
import type { VisualizeServices } from './visualize_app/types';
import {
@ -166,6 +167,7 @@ export interface VisualizationsStartDeps {
savedObjectsManagement: SavedObjectsManagementPluginStart;
contentManagement: ContentManagementPublicStart;
serverless?: ServerlessPluginStart;
noDataPage?: NoDataPagePluginStart;
}
/**
@ -330,6 +332,7 @@ export class VisualizationsPlugin
listingViewRegistry,
unifiedSearch: pluginsStart.unifiedSearch,
serverless: pluginsStart.serverless,
noDataPage: pluginsStart.noDataPage,
};
params.element.classList.add('visAppWrapper');

View file

@ -14,6 +14,7 @@ import { EuiLoadingSpinner } from '@elastic/eui';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public';
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
AnalyticsNoDataPageKibanaProvider,
@ -38,6 +39,7 @@ interface NoDataComponentProps {
dataViews: DataViewsContract;
dataViewEditor: DataViewEditorStart;
onDataViewCreated: (dataView: unknown) => void;
noDataPage?: NoDataPagePluginStart;
}
const NoDataComponent = ({
@ -45,11 +47,13 @@ const NoDataComponent = ({
dataViews,
dataViewEditor,
onDataViewCreated,
noDataPage,
}: NoDataComponentProps) => {
const analyticsServices = {
coreStart: core,
dataViews,
dataViewEditor,
noDataPage,
};
return (
<AnalyticsNoDataPageKibanaProvider {...analyticsServices}>
@ -65,6 +69,7 @@ export const VisualizeApp = ({ onAppLeave }: VisualizeAppProps) => {
core,
kbnUrlStateStorage,
dataViewEditor,
noDataPage,
},
} = useKibana<VisualizeServices>();
const { pathname } = useLocation();
@ -125,6 +130,7 @@ export const VisualizeApp = ({ onAppLeave }: VisualizeAppProps) => {
dataViewEditor={dataViewEditor}
dataViews={dataViews}
onDataViewCreated={onDataViewCreated}
noDataPage={noDataPage}
/>
);
}

View file

@ -41,6 +41,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { SavedSearch, SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
import type {
Vis,
VisualizeEmbeddableContract,
@ -117,6 +118,7 @@ export interface VisualizeServices extends CoreStart {
listingViewRegistry: ListingViewRegistry;
unifiedSearch: UnifiedSearchPublicPluginStart;
serverless?: ServerlessPluginStart;
noDataPage?: NoDataPagePluginStart;
}
export interface VisInstance {

View file

@ -62,7 +62,8 @@
"@kbn/content-management-tabbed-table-list-view",
"@kbn/content-management-table-list-view",
"@kbn/content-management-utils",
"@kbn/serverless"
"@kbn/serverless",
"@kbn/no-data-page-plugin"
],
"exclude": [
"target/**/*",

View file

@ -145,6 +145,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'newsfeed.mainInterval (duration)',
'newsfeed.service.pathTemplate (string)',
'newsfeed.service.urlRoot (string)',
'no_data_page.analyticsNoDataPageFlavor (any)', // It's a string (any because schema.conditional)
'telemetry.allowChangingOptInStatus (boolean)',
'telemetry.appendServerlessChannelsSuffix (any)', // It's a boolean (any because schema.conditional)
'telemetry.banner (boolean)',

View file

@ -1028,6 +1028,8 @@
"@kbn/newsfeed-plugin/*": ["src/plugins/newsfeed/*"],
"@kbn/newsfeed-test-plugin": ["test/common/plugins/newsfeed"],
"@kbn/newsfeed-test-plugin/*": ["test/common/plugins/newsfeed/*"],
"@kbn/no-data-page-plugin": ["src/plugins/no_data_page"],
"@kbn/no-data-page-plugin/*": ["src/plugins/no_data_page/*"],
"@kbn/notifications-plugin": ["x-pack/plugins/notifications"],
"@kbn/notifications-plugin/*": ["x-pack/plugins/notifications/*"],
"@kbn/object-versioning": ["packages/kbn-object-versioning"],

View file

@ -0,0 +1,41 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObject, getService }: FtrProviderContext) {
const svlSearchNavigation = getService('svlSearchNavigation');
const testSubjects = getService('testSubjects');
const svlCommonNavigation = getPageObject('svlCommonNavigation');
describe('empty pages', function () {
before(async () => {
await svlSearchNavigation.navigateToLandingPage();
});
it('should show search specific empty page in discover', async () => {
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' });
await testSubjects.existOrFail('kbnOverviewElasticsearchGettingStarted');
await testSubjects.click('kbnOverviewElasticsearchGettingStarted');
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Getting started' });
});
it('should show search specific empty page in visualize', async () => {
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'visualize' });
await testSubjects.existOrFail('kbnOverviewElasticsearchGettingStarted');
await testSubjects.click('kbnOverviewElasticsearchGettingStarted');
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Getting started' });
});
it('should show search specific empty page in dashboards', async () => {
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'dashboards' });
await testSubjects.existOrFail('kbnOverviewElasticsearchGettingStarted');
await testSubjects.click('kbnOverviewElasticsearchGettingStarted');
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Getting started' });
});
});
}

View file

@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless search UI', function () {
loadTestFile(require.resolve('./landing_page'));
loadTestFile(require.resolve('./empty_page'));
loadTestFile(require.resolve('./navigation'));
loadTestFile(require.resolve('./cases/attachment_framework'));
});

View file

@ -4962,6 +4962,10 @@
version "0.0.0"
uid ""
"@kbn/no-data-page-plugin@link:src/plugins/no_data_page":
version "0.0.0"
uid ""
"@kbn/notifications-plugin@link:x-pack/plugins/notifications":
version "0.0.0"
uid ""