Kibana developer examples landing page (#67049) (#68705)

* Kibana developer examples

* Batch explorer tests should be run in examples config

* Fix tests

* add codeowner for new developer examples plugin & readme cleanup

* Try to frame embeddable wording based on what a developer's goals are.

* Add noopener noreferer, fix bad merge

* Remove bfetch.png

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
# Conflicts:
#	.github/CODEOWNERS
This commit is contained in:
Stacey Gammon 2020-06-10 07:55:11 -04:00 committed by GitHub
parent e8cc6b423b
commit 86ea134591
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 509 additions and 109 deletions

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": true, "server": true,
"ui": true, "ui": true,
"requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions"], "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public';
import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/public'; import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/public';
import { ChartsPluginStart } from '../../../src/plugins/charts/public'; import { ChartsPluginStart } from '../../../src/plugins/charts/public';
import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public';
@ -25,6 +25,7 @@ import { DataPublicPluginStart } from '../../../src/plugins/data/public';
import { getAlertType as getAlwaysFiringAlertType } from './alert_types/always_firing'; import { getAlertType as getAlwaysFiringAlertType } from './alert_types/always_firing';
import { getAlertType as getPeopleInSpaceAlertType } from './alert_types/astros'; import { getAlertType as getPeopleInSpaceAlertType } from './alert_types/astros';
import { registerNavigation } from './alert_types'; import { registerNavigation } from './alert_types';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
export type Setup = void; export type Setup = void;
export type Start = void; export type Start = void;
@ -32,6 +33,7 @@ export type Start = void;
export interface AlertingExamplePublicSetupDeps { export interface AlertingExamplePublicSetupDeps {
alerts: AlertingSetup; alerts: AlertingSetup;
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
developerExamples: DeveloperExamplesSetup;
} }
export interface AlertingExamplePublicStartDeps { export interface AlertingExamplePublicStartDeps {
@ -44,11 +46,12 @@ export interface AlertingExamplePublicStartDeps {
export class AlertingExamplePlugin implements Plugin<Setup, Start, AlertingExamplePublicSetupDeps> { export class AlertingExamplePlugin implements Plugin<Setup, Start, AlertingExamplePublicSetupDeps> {
public setup( public setup(
core: CoreSetup<AlertingExamplePublicStartDeps, Start>, core: CoreSetup<AlertingExamplePublicStartDeps, Start>,
{ alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps { alerts, triggers_actions_ui, developerExamples }: AlertingExamplePublicSetupDeps
) { ) {
core.application.register({ core.application.register({
id: 'AlertingExample', id: 'AlertingExample',
title: 'Alerting Example', title: 'Alerting Example',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices(); const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./application'); const { renderApp } = await import('./application');
@ -60,6 +63,21 @@ export class AlertingExamplePlugin implements Plugin<Setup, Start, AlertingExamp
triggers_actions_ui.alertTypeRegistry.register(getPeopleInSpaceAlertType()); triggers_actions_ui.alertTypeRegistry.register(getPeopleInSpaceAlertType());
registerNavigation(alerts); registerNavigation(alerts);
developerExamples.register({
appId: 'AlertingExample',
title: 'Alerting',
description: `This alerting example walks you through how to set up a new alert.`,
links: [
{
label: 'README',
href: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
} }
public start() {} public start() {}

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": true, "server": true,
"ui": true, "ui": true,
"requiredPlugins": ["bfetch"], "requiredPlugins": ["bfetch", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -17,9 +17,10 @@
* under the License. * under the License.
*/ */
import { Plugin, CoreSetup } from 'kibana/public'; import { Plugin, CoreSetup, AppNavLinkStatus } from '../../../src/core/public';
import { BfetchPublicSetup, BfetchPublicStart } from '../../../src/plugins/bfetch/public'; import { BfetchPublicSetup, BfetchPublicStart } from '../../../src/plugins/bfetch/public';
import { mount } from './mount'; import { mount } from './mount';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
export interface ExplorerService { export interface ExplorerService {
double: (number: { num: number }) => Promise<{ num: number }>; double: (number: { num: number }) => Promise<{ num: number }>;
@ -27,6 +28,7 @@ export interface ExplorerService {
export interface BfetchExplorerSetupPlugins { export interface BfetchExplorerSetupPlugins {
bfetch: BfetchPublicSetup; bfetch: BfetchPublicSetup;
developerExamples: DeveloperExamplesSetup;
} }
export interface BfetchExplorerStartPlugins { export interface BfetchExplorerStartPlugins {
@ -36,9 +38,9 @@ export interface BfetchExplorerStartPlugins {
export class BfetchExplorerPlugin implements Plugin { export class BfetchExplorerPlugin implements Plugin {
public setup( public setup(
core: CoreSetup<BfetchExplorerStartPlugins, void>, core: CoreSetup<BfetchExplorerStartPlugins, void>,
plugins: BfetchExplorerSetupPlugins { bfetch, developerExamples }: BfetchExplorerSetupPlugins
) { ) {
const double = plugins.bfetch.batchedFunction<{ num: number }, { num: number }>({ const double = bfetch.batchedFunction<{ num: number }, { num: number }>({
url: '/bfetch_explorer/double', url: '/bfetch_explorer/double',
}); });
@ -49,8 +51,25 @@ export class BfetchExplorerPlugin implements Plugin {
core.application.register({ core.application.register({
id: 'bfetch-explorer', id: 'bfetch-explorer',
title: 'bfetch explorer', title: 'bfetch explorer',
navLinkStatus: AppNavLinkStatus.hidden,
mount: mount(core, explorer), mount: mount(core, explorer),
}); });
developerExamples.register({
appId: 'bfetch-explorer',
title: 'bfetch',
description:
'bfetch is a service that allows to batch HTTP requests and streams responses back.',
links: [
{
label: 'README',
href: 'https://github.com/elastic/kibana/blob/master/src/plugins/bfetch/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
} }
public start() {} public start() {}

View file

@ -0,0 +1,36 @@
## Developer examples
Owner: Kibana application architecture team
The developer examples app is a landing page where developers go to search for working, tested examples of various developer
services. Add your a link to your example using the developerExamples `register` function offered on the `setup` contract:
```ts
setup(core, { developerExamples }) {
developerExamples.register({
appId: 'myFooExampleApp',
title: 'Foo services',
description: `Foo services let you do bar and zed.`,
links: [
{
label: 'README',
href: 'https://github.com/elastic/kibana/tree/master/src/plugins/foo/README.md',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
],
image: img,
});
}
```
Run Kibana with developer examples via:
```
yarn start --run-examples
```
Then navigate to "Developer examples":
<img src="./navigation.png" height="400px" />

View file

@ -0,0 +1,9 @@
{
"id": "developerExamples",
"version": "0.0.1",
"kibanaVersion": "kibana",
"server": false,
"ui": true,
"requiredPlugins": [],
"optionalPlugins": []
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,117 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import {
EuiText,
EuiPageContent,
EuiCard,
EuiPageContentHeader,
EuiFlexGroup,
EuiFlexItem,
EuiFieldSearch,
EuiListGroup,
EuiHighlight,
EuiLink,
EuiButtonIcon,
} from '@elastic/eui';
import { AppMountParameters } from '../../../src/core/public';
import { ExampleDefinition } from './types';
interface Props {
examples: ExampleDefinition[];
navigateToApp: (appId: string) => void;
getUrlForApp: (appId: string) => string;
}
function DeveloperExamples({ examples, navigateToApp, getUrlForApp }: Props) {
const [search, setSearch] = useState<string>('');
const lcSearch = search.toLowerCase();
const filteredExamples = !lcSearch
? examples
: examples.filter((def) => {
if (def.description.toLowerCase().indexOf(lcSearch) >= 0) return true;
if (def.title.toLowerCase().indexOf(lcSearch) >= 0) return true;
return false;
});
return (
<EuiPageContent>
<EuiPageContentHeader>
<EuiText>
<h1>Developer examples</h1>
<p>
The following examples showcase services and APIs that are available to developers.
<EuiFieldSearch
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
isClearable={true}
aria-label="Search developer examples"
/>
</p>
</EuiText>
</EuiPageContentHeader>
<EuiFlexGroup wrap>
{filteredExamples.map((def) => (
<EuiFlexItem style={{ minWidth: 300, maxWidth: 500 }} key={def.appId}>
<EuiCard
description={
<EuiHighlight search={search} highlightAll={true}>
{def.description}
</EuiHighlight>
}
title={
<React.Fragment>
<EuiLink
onClick={() => {
navigateToApp(def.appId);
}}
>
<EuiHighlight search={search} highlightAll={true}>
{def.title}
</EuiHighlight>
</EuiLink>
<EuiButtonIcon
iconType="popout"
onClick={() =>
window.open(getUrlForApp(def.appId), '_blank', 'noopener, noreferrer')
}
>
Open in new tab
</EuiButtonIcon>
</React.Fragment>
}
image={def.image}
footer={def.links ? <EuiListGroup size={'s'} listItems={def.links} /> : undefined}
/>
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiPageContent>
);
}
export const renderApp = (props: Props, element: AppMountParameters['element']) => {
ReactDOM.render(<DeveloperExamples {...props} />, element);
return () => ReactDOM.unmountComponentAtNode(element);
};

View file

@ -17,4 +17,8 @@
* under the License. * under the License.
*/ */
export * from '../../../../../examples/bfetch_explorer/public'; import { DeveloperExamplesPlugin } from './plugin';
export const plugin = () => new DeveloperExamplesPlugin();
export { DeveloperExamplesSetup } from './plugin';

View file

@ -0,0 +1,68 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
CoreSetup,
Plugin,
AppMountParameters,
DEFAULT_APP_CATEGORIES,
} from '../../../src/core/public';
import { ExampleDefinition } from './types';
export interface DeveloperExamplesSetup {
register: (def: ExampleDefinition) => void;
}
export class DeveloperExamplesPlugin implements Plugin<DeveloperExamplesSetup, void> {
private examplesRegistry: ExampleDefinition[] = [];
public setup(core: CoreSetup) {
const examples = this.examplesRegistry;
core.application.register({
id: 'developerExamples',
title: 'Developer examples',
order: -2000,
category: DEFAULT_APP_CATEGORIES.kibana,
async mount(params: AppMountParameters) {
const { renderApp } = await import('./app');
const [coreStart] = await core.getStartServices();
return renderApp(
{
examples,
navigateToApp: (appId: string) => coreStart.application.navigateToApp(appId),
getUrlForApp: (appId: string) => coreStart.application.getUrlForApp(appId),
},
params.element
);
},
});
const api: DeveloperExamplesSetup = {
register: (def) => {
this.examplesRegistry.push(def);
},
};
return api;
}
public start() {}
public stop() {}
}

View file

@ -17,4 +17,18 @@
* under the License. * under the License.
*/ */
export * from '../../../../../examples/bfetch_explorer/server'; import { EuiListGroupItemProps } from '@elastic/eui';
export interface ExampleDefinition {
/**
* The application id that is the landing page for the example.
*/
appId: string;
title: string;
description: string;
image?: string;
/**
* Any additional links you want to show, for example to the github README.
*/
links?: EuiListGroupItemProps[];
}

View file

@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"../../typings/**/*",
],
"exclude": []
}

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": false, "server": false,
"ui": true, "ui": true,
"requiredPlugins": ["uiActions", "inspector", "embeddable", "embeddableExamples"], "requiredPlugins": ["uiActions", "inspector", "embeddable", "embeddableExamples", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -16,12 +16,13 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
import { EmbeddableExamplesStart } from 'examples/embeddable_examples/public/plugin'; import { EmbeddableExamplesStart } from 'examples/embeddable_examples/public/plugin';
import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public';
import { UiActionsService } from '../../../src/plugins/ui_actions/public'; import { UiActionsService } from '../../../src/plugins/ui_actions/public';
import { EmbeddableStart } from '../../../src/plugins/embeddable/public'; import { EmbeddableStart } from '../../../src/plugins/embeddable/public';
import { Start as InspectorStart } from '../../../src/plugins/inspector/public'; import { Start as InspectorStart } from '../../../src/plugins/inspector/public';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
import img from './embeddables.png';
interface StartDeps { interface StartDeps {
uiActions: UiActionsService; uiActions: UiActionsService;
@ -30,11 +31,16 @@ interface StartDeps {
embeddableExamples: EmbeddableExamplesStart; embeddableExamples: EmbeddableExamplesStart;
} }
interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
}
export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDeps> { export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDeps> {
public setup(core: CoreSetup<StartDeps>) { public setup(core: CoreSetup<StartDeps>, { developerExamples }: SetupDeps) {
core.application.register({ core.application.register({
id: 'embeddableExplorer', id: 'embeddableExplorer',
title: 'Embeddable explorer', title: 'Embeddable explorer',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices(); const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app'); const { renderApp } = await import('./app');
@ -55,6 +61,25 @@ export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDep
); );
}, },
}); });
developerExamples.register({
appId: 'embeddableExplorer',
title: 'Embeddables',
description: `Multiple embeddable examples showcase how to build custom dashboard widgets, how to build your own custom "container"
(like a dashboard but imagine you want to render the panels differently), and how to embed anything that can show up in a dashboard
in your own UI and app, that comes pre-connected with actions built by other developers.
`,
links: [
{
label: 'README',
href: 'https://github.com/elastic/kibana/tree/master/src/plugins/embeddable/README.md',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
],
image: img,
});
} }
public start() {} public start() {}

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": false, "server": false,
"ui": true, "ui": true,
"requiredPlugins": ["data", "demoSearch"], "requiredPlugins": ["data", "demoSearch", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -17,20 +17,47 @@
* under the License. * under the License.
*/ */
import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public';
import { AppPluginStartDependencies } from './types'; import { AppPluginStartDependencies } from './types';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
}
export class SearchExplorerPlugin implements Plugin { export class SearchExplorerPlugin implements Plugin {
public setup(core: CoreSetup<AppPluginStartDependencies, void>) { public setup(
core: CoreSetup<AppPluginStartDependencies, void>,
{ developerExamples }: SetupDeps
) {
core.application.register({ core.application.register({
id: 'searchExplorer', id: 'searchExplorer',
title: 'Search Explorer', title: 'Search Explorer',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices(); const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./application'); const { renderApp } = await import('./application');
return renderApp(coreStart, depsStart, params); return renderApp(coreStart, depsStart, params);
}, },
}); });
developerExamples.register({
appId: 'searchExplorer',
title: 'Data search strategy services',
description: `Data search services can be used to query Elasticsearch in away that supports background search
and partial results, when available. It also automatically incorporates settings such as requestTimeout and includeFrozen.
Use the provided ES search strategy, or register your own.
`,
links: [
{
label: 'README',
href:
'https://github.com/elastic/kibana/blob/master/src/plugins/data/public/search/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
} }
public start() {} public start() {}

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": true, "server": true,
"ui": true, "ui": true,
"requiredPlugins": ["navigation", "data"], "requiredPlugins": ["navigation", "data", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -17,15 +17,21 @@
* under the License. * under the License.
*/ */
import { AppMountParameters, CoreSetup, Plugin } from 'kibana/public'; import { AppMountParameters, CoreSetup, Plugin, AppNavLinkStatus } from '../../../src/core/public';
import { AppPluginDependencies } from './with_data_services/types'; import { AppPluginDependencies } from './with_data_services/types';
import { PLUGIN_ID, PLUGIN_NAME } from '../common'; import { PLUGIN_ID, PLUGIN_NAME } from '../common';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
}
export class StateContainersExamplesPlugin implements Plugin { export class StateContainersExamplesPlugin implements Plugin {
public setup(core: CoreSetup) { public setup(core: CoreSetup, { developerExamples }: SetupDeps) {
core.application.register({ core.application.register({
id: 'stateContainersExampleBrowserHistory', id: 'stateContainersExampleBrowserHistory',
title: 'State containers example - browser history routing', title: 'State containers example - browser history routing',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const { renderApp, History } = await import('./todo/app'); const { renderApp, History } = await import('./todo/app');
return renderApp(params, { return renderApp(params, {
@ -38,6 +44,7 @@ export class StateContainersExamplesPlugin implements Plugin {
core.application.register({ core.application.register({
id: 'stateContainersExampleHashHistory', id: 'stateContainersExampleHashHistory',
title: 'State containers example - hash history routing', title: 'State containers example - hash history routing',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const { renderApp, History } = await import('./todo/app'); const { renderApp, History } = await import('./todo/app');
return renderApp(params, { return renderApp(params, {
@ -51,6 +58,7 @@ export class StateContainersExamplesPlugin implements Plugin {
core.application.register({ core.application.register({
id: PLUGIN_ID, id: PLUGIN_ID,
title: PLUGIN_NAME, title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
// Load application bundle // Load application bundle
const { renderApp } = await import('./with_data_services/application'); const { renderApp } = await import('./with_data_services/application');
@ -60,6 +68,62 @@ export class StateContainersExamplesPlugin implements Plugin {
return renderApp(coreStart, depsStart as AppPluginDependencies, params); return renderApp(coreStart, depsStart as AppPluginDependencies, params);
}, },
}); });
developerExamples.register({
appId: 'stateContainersExampleBrowserHistory',
title: 'State containers using browser history',
description: `An example todo app that uses browser history and state container utilities like createStateContainerReactHelpers,
createStateContainer, createKbnUrlStateStorage, createSessionStorageStateStorage,
syncStates and getStateFromKbnUrl to keep state in sync with the URL. Change some parameters, navigate away and then back, and the
state should be preserved.`,
links: [
{
label: 'README',
href:
'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
developerExamples.register({
appId: 'stateContainersExampleHashHistory',
title: 'State containers using hash history',
description: `An example todo app that uses hash history and state container utilities like createStateContainerReactHelpers,
createStateContainer, createKbnUrlStateStorage, createSessionStorageStateStorage,
syncStates and getStateFromKbnUrl to keep state in sync with the URL. Change some parameters, navigate away and then back, and the
state should be preserved.`,
links: [
{
label: 'README',
href:
'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
developerExamples.register({
appId: PLUGIN_ID,
title: 'Sync state from a query bar with the url',
description: `Shows how to use data.syncQueryStateWitUrl in combination with state container utilities from kibana_utils to
show a query bar that stores state in the url and is kept in sync.
`,
links: [
{
label: 'README',
href:
'https://github.com/elastic/kibana/blob/master/src/plugins/data/public/query/state_sync/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
} }
public start() {} public start() {}

View file

@ -27,7 +27,7 @@ import { createKbnUrlStateStorage } from '../../../../src/plugins/kibana_utils/p
export const renderApp = ( export const renderApp = (
{ notifications, http }: CoreStart, { notifications, http }: CoreStart,
{ navigation, data }: AppPluginDependencies, { navigation, data }: AppPluginDependencies,
{ appBasePath, element, history }: AppMountParameters { element, history }: AppMountParameters
) => { ) => {
const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history }); const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history });

View file

@ -78,14 +78,7 @@ const {
useContainer: useAppStateContainer, useContainer: useAppStateContainer,
} = createStateContainerReactHelpers<ReduxLikeStateContainer<AppState>>(); } = createStateContainerReactHelpers<ReduxLikeStateContainer<AppState>>();
const App = ({ const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps) => {
notifications,
http,
navigation,
data,
history,
kbnUrlStateStorage,
}: StateDemoAppDeps) => {
const appStateContainer = useAppStateContainer(); const appStateContainer = useAppStateContainer();
const appState = useAppState(); const appState = useAppState();

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": false, "server": false,
"ui": true, "ui": true,
"requiredPlugins": ["uiActions", "uiActionsExamples"], "requiredPlugins": ["uiActions", "uiActionsExamples", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -17,8 +17,8 @@
* under the License. * under the License.
*/ */
import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { UiActionsStart, UiActionsSetup } from '../../../src/plugins/ui_actions/public';
import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public';
import { import {
PHONE_TRIGGER, PHONE_TRIGGER,
USER_TRIGGER, USER_TRIGGER,
@ -39,6 +39,8 @@ import {
ACTION_VIEW_IN_MAPS, ACTION_VIEW_IN_MAPS,
ACTION_PHONE_USER, ACTION_PHONE_USER,
} from './actions/actions'; } from './actions/actions';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
import image from './ui_actions.png';
interface StartDeps { interface StartDeps {
uiActions: UiActionsStart; uiActions: UiActionsStart;
@ -46,6 +48,7 @@ interface StartDeps {
interface SetupDeps { interface SetupDeps {
uiActions: UiActionsSetup; uiActions: UiActionsSetup;
developerExamples: DeveloperExamplesSetup;
} }
declare module '../../../src/plugins/ui_actions/public' { declare module '../../../src/plugins/ui_actions/public' {
@ -66,7 +69,7 @@ declare module '../../../src/plugins/ui_actions/public' {
} }
export class UiActionsExplorerPlugin implements Plugin<void, void, {}, StartDeps> { export class UiActionsExplorerPlugin implements Plugin<void, void, {}, StartDeps> {
public setup(core: CoreSetup<{ uiActions: UiActionsStart }>, deps: SetupDeps) { public setup(core: CoreSetup<StartDeps>, deps: SetupDeps) {
deps.uiActions.registerTrigger({ deps.uiActions.registerTrigger({
id: COUNTRY_TRIGGER, id: COUNTRY_TRIGGER,
}); });
@ -98,6 +101,7 @@ export class UiActionsExplorerPlugin implements Plugin<void, void, {}, StartDeps
core.application.register({ core.application.register({
id: 'uiActionsExplorer', id: 'uiActionsExplorer',
title: 'Ui Actions Explorer', title: 'Ui Actions Explorer',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices(); const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app'); const { renderApp } = await import('./app');
@ -107,6 +111,24 @@ export class UiActionsExplorerPlugin implements Plugin<void, void, {}, StartDeps
); );
}, },
}); });
deps.developerExamples.register({
appId: 'uiActionsExplorer',
title: 'Ui Actions & Triggers',
description: `Ui Actions can be used to make any part of your UI extensible. It has built in support for
context menus, but you can also render all actions attached to a given trigger however you like, just how
panel badges and panel notifications does.`,
image,
links: [
{
label: 'README',
href: 'https://github.com/elastic/kibana/blob/master/src/plugins/ui_actions/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
} }
public start() {} public start() {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View file

@ -18,7 +18,7 @@
*/ */
import { SharePluginStart, SharePluginSetup } from '../../../src/plugins/share/public'; import { SharePluginStart, SharePluginSetup } from '../../../src/plugins/share/public';
import { Plugin, CoreSetup, AppMountParameters } from '../../../src/core/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public';
import { import {
HelloLinkGeneratorState, HelloLinkGeneratorState,
createHelloPageLinkGenerator, createHelloPageLinkGenerator,
@ -58,6 +58,7 @@ export class AccessLinksExamplesPlugin implements Plugin<void, void, SetupDeps,
core.application.register({ core.application.register({
id: APP_ID, id: APP_ID,
title: 'Access links examples', title: 'Access links examples',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const { renderApp } = await import('./app'); const { renderApp } = await import('./app');
return renderApp( return renderApp(

View file

@ -4,6 +4,6 @@
"kibanaVersion": "kibana", "kibanaVersion": "kibana",
"server": false, "server": false,
"ui": true, "ui": true,
"requiredPlugins": ["share", "urlGeneratorsExamples"], "requiredPlugins": ["share", "urlGeneratorsExamples", "developerExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -16,19 +16,24 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { SharePluginStart } from '../../../src/plugins/share/public';
import { SharePluginStart } from 'src/plugins/share/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public';
import { Plugin, CoreSetup, AppMountParameters } from '../../../src/core/public'; import { DeveloperExamplesSetup } from '../../developer_examples/public';
interface StartDeps { interface StartDeps {
share: SharePluginStart; share: SharePluginStart;
} }
export class AccessLinksExplorerPlugin implements Plugin<void, void, {}, StartDeps> { interface SetupDeps {
public setup(core: CoreSetup<StartDeps>) { developerExamples: DeveloperExamplesSetup;
}
export class AccessLinksExplorerPlugin implements Plugin<void, void, SetupDeps, StartDeps> {
public setup(core: CoreSetup<StartDeps>, { developerExamples }: SetupDeps) {
core.application.register({ core.application.register({
id: 'urlGeneratorsExplorer', id: 'urlGeneratorsExplorer',
title: 'Access links explorer', title: 'Access links explorer',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const depsStart = (await core.getStartServices())[1]; const depsStart = (await core.getStartServices())[1];
const { renderApp } = await import('./app'); const { renderApp } = await import('./app');
@ -40,6 +45,26 @@ export class AccessLinksExplorerPlugin implements Plugin<void, void, {}, StartDe
); );
}, },
}); });
developerExamples.register({
title: 'Url generators',
description: `Url generators offer are a backward compatible safe way to persist a URL in a saved object.
Store the url generator id and state, instead of the href string. When the link is recreated at run time, the service
will run the state through any necessary migrations. Registrators can change their URL structure
without breaking these links stored in saved objects.
`,
appId: 'urlGeneratorsExplorer',
links: [
{
label: 'README',
href:
'https://github.com/elastic/kibana/blob/master/src/plugins/share/public/url_generators/README.md',
iconType: 'logoGithub',
size: 's',
target: '_blank',
},
],
});
} }
public start() {} public start() {}

View file

@ -18,15 +18,14 @@
*/ */
import expect from '@kbn/expect'; import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../functional/ftr_provider_context'; import { FtrProviderContext } from '../../functional/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function ({ getService }: FtrProviderContext) { export default function ({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects'); const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
describe('batchedFunction', () => { describe('batchedFunction', () => {
beforeEach(async () => { beforeEach(async () => {
await appsMenu.clickLink('bfetch explorer');
await testSubjects.click('count-until'); await testSubjects.click('count-until');
await testSubjects.click('double-integers'); await testSubjects.click('double-integers');
}); });

View file

@ -17,18 +17,17 @@
* under the License. * under the License.
*/ */
import { FtrProviderContext } from '../../../functional/ftr_provider_context'; import { FtrProviderContext } from '../../functional/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) {
const browser = getService('browser'); const browser = getService('browser');
const appsMenu = getService('appsMenu');
const PageObjects = getPageObjects(['common', 'header']); const PageObjects = getPageObjects(['common', 'header']);
describe('bfetch explorer', function () { describe('bfetch explorer', function () {
before(async () => { before(async () => {
await browser.setWindowSize(1300, 900); await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings'); await PageObjects.common.navigateToApp('bfetch-explorer', { insertTimestamp: false });
await appsMenu.clickLink('bfetch explorer');
}); });
loadTestFile(require.resolve('./batched_function')); loadTestFile(require.resolve('./batched_function'));

View file

@ -27,6 +27,7 @@ export default async function ({ readConfigFile }) {
testFiles: [ testFiles: [
require.resolve('./search'), require.resolve('./search'),
require.resolve('./embeddables'), require.resolve('./embeddables'),
require.resolve('./bfetch_explorer'),
require.resolve('./ui_actions'), require.resolve('./ui_actions'),
require.resolve('./state_sync'), require.resolve('./state_sync'),
], ],

View file

@ -26,14 +26,12 @@ export default function ({
loadTestFile, loadTestFile,
}: PluginFunctionalProviderContext) { }: PluginFunctionalProviderContext) {
const browser = getService('browser'); const browser = getService('browser');
const appsMenu = getService('appsMenu');
const PageObjects = getPageObjects(['common', 'header']); const PageObjects = getPageObjects(['common', 'header']);
describe('embeddable explorer', function () { describe('embeddable explorer', function () {
before(async () => { before(async () => {
await browser.setWindowSize(1300, 900); await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings'); await PageObjects.common.navigateToApp('embeddableExplorer');
await appsMenu.clickLink('Embeddable explorer');
}); });
loadTestFile(require.resolve('./hello_world_embeddable')); loadTestFile(require.resolve('./hello_world_embeddable'));

View file

@ -22,7 +22,6 @@ import { FtrProviderContext } from 'test/functional/ftr_provider_context';
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) {
const browser = getService('browser'); const browser = getService('browser');
const appsMenu = getService('appsMenu');
const esArchiver = getService('esArchiver'); const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer'); const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'header']); const PageObjects = getPageObjects(['common', 'header']);
@ -36,8 +35,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
defaultIndex: 'logstash-*', defaultIndex: 'logstash-*',
}); });
await browser.setWindowSize(1300, 900); await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings'); await PageObjects.common.navigateToApp('searchExplorer');
await appsMenu.clickLink('Search Explorer');
}); });
after(async function () { after(async function () {

View file

@ -26,12 +26,10 @@ export default function ({
loadTestFile, loadTestFile,
}: PluginFunctionalProviderContext) { }: PluginFunctionalProviderContext) {
const browser = getService('browser'); const browser = getService('browser');
const PageObjects = getPageObjects(['common']);
describe('state sync examples', function () { describe('state sync examples', function () {
before(async () => { before(async () => {
await browser.setWindowSize(1300, 900); await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings');
}); });
loadTestFile(require.resolve('./todo_app')); loadTestFile(require.resolve('./todo_app'));

View file

@ -26,7 +26,6 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const testSubjects = getService('testSubjects'); const testSubjects = getService('testSubjects');
const find = getService('find'); const find = getService('find');
const retry = getService('retry'); const retry = getService('retry');
const appsMenu = getService('appsMenu');
const browser = getService('browser'); const browser = getService('browser');
const PageObjects = getPageObjects(['common']); const PageObjects = getPageObjects(['common']);
const log = getService('log'); const log = getService('log');
@ -38,7 +37,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
before(async () => { before(async () => {
base = await PageObjects.common.getHostPort(); base = await PageObjects.common.getHostPort();
await appsMenu.clickLink('State containers example - browser history routing'); await PageObjects.common.navigateToApp(appId, { insertTimestamp: false });
}); });
it('links are rendered correctly and state is preserved in links', async () => { it('links are rendered correctly and state is preserved in links', async () => {
@ -119,7 +118,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
describe('TODO app with hash history ', async () => { describe('TODO app with hash history ', async () => {
before(async () => { before(async () => {
await appsMenu.clickLink('State containers example - hash history routing'); await PageObjects.common.navigateToApp('stateContainersExampleHashHistory', {
insertTimestamp: false,
});
}); });
it('Links are rendered correctly and state is preserved in links', async () => { it('Links are rendered correctly and state is preserved in links', async () => {

View file

@ -26,14 +26,12 @@ export default function ({
loadTestFile, loadTestFile,
}: PluginFunctionalProviderContext) { }: PluginFunctionalProviderContext) {
const browser = getService('browser'); const browser = getService('browser');
const appsMenu = getService('appsMenu');
const PageObjects = getPageObjects(['common', 'header']); const PageObjects = getPageObjects(['common', 'header']);
describe('ui actions explorer', function () { describe('ui actions explorer', function () {
before(async () => { before(async () => {
await browser.setWindowSize(1300, 900); await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings'); await PageObjects.common.navigateToApp('uiActionsExplorer');
await appsMenu.clickLink('Ui Actions Explorer');
}); });
loadTestFile(require.resolve('./ui_actions')); loadTestFile(require.resolve('./ui_actions'));

View file

@ -37,7 +37,6 @@ export default async function ({ readConfigFile }) {
require.resolve('./test_suites/embeddable_explorer'), require.resolve('./test_suites/embeddable_explorer'),
require.resolve('./test_suites/core_plugins'), require.resolve('./test_suites/core_plugins'),
require.resolve('./test_suites/management'), require.resolve('./test_suites/management'),
require.resolve('./test_suites/bfetch_explorer'),
require.resolve('./test_suites/doc_views'), require.resolve('./test_suites/doc_views'),
], ],
services: { services: {

View file

@ -1,10 +0,0 @@
{
"id": "kbn_tp_bfetch_explorer",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["kbn_tp_bfetch_explorer"],
"server": true,
"ui": true,
"requiredPlugins": ["bfetch"],
"optionalPlugins": []
}

View file

@ -1,17 +0,0 @@
{
"name": "kbn_tp_bfetch_explorer",
"version": "1.0.0",
"main": "target/examples/kbn_tp_bfetch_explorer",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.7.2"
}
}

View file

@ -1,21 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true,
"types": [
"node",
"jest",
"react"
]
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"server/**/*.tsx",
"../../../../typings/**/*",
],
"exclude": []
}