Skip to content

Commit

Permalink
Kibana developer examples landing page (#67049)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
stacey-gammon and elasticmachine committed Jun 8, 2020
1 parent c29fcbb commit f89e911
Show file tree
Hide file tree
Showing 40 changed files with 510 additions and 109 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui

# App Architecture
/examples/developer_examples/ @elastic/kibana-app-arch
/examples/url_generators_examples/ @elastic/kibana-app-arch
/examples/url_generators_explorer/ @elastic/kibana-app-arch
/packages/kbn-interpreter/ @elastic/kibana-app-arch
Expand Down
2 changes: 1 addition & 1 deletion examples/alerting_example/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions"],
"requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions", "developerExamples"],
"optionalPlugins": []
}
22 changes: 20 additions & 2 deletions examples/alerting_example/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,23 @@
* 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 { ChartsPluginStart } from '../../../src/plugins/charts/public';
import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public';
import { DataPublicPluginStart } from '../../../src/plugins/data/public';
import { getAlertType as getAlwaysFiringAlertType } from './alert_types/always_firing';
import { getAlertType as getPeopleInSpaceAlertType } from './alert_types/astros';
import { registerNavigation } from './alert_types';
import { DeveloperExamplesSetup } from '../../developer_examples/public';

export type Setup = void;
export type Start = void;

export interface AlertingExamplePublicSetupDeps {
alerts: AlertingSetup;
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
developerExamples: DeveloperExamplesSetup;
}

export interface AlertingExamplePublicStartDeps {
Expand All @@ -44,11 +46,12 @@ export interface AlertingExamplePublicStartDeps {
export class AlertingExamplePlugin implements Plugin<Setup, Start, AlertingExamplePublicSetupDeps> {
public setup(
core: CoreSetup<AlertingExamplePublicStartDeps, Start>,
{ alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps
{ alerts, triggers_actions_ui, developerExamples }: AlertingExamplePublicSetupDeps
) {
core.application.register({
id: 'AlertingExample',
title: 'Alerting Example',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./application');
Expand All @@ -60,6 +63,21 @@ export class AlertingExamplePlugin implements Plugin<Setup, Start, AlertingExamp
triggers_actions_ui.alertTypeRegistry.register(getPeopleInSpaceAlertType());

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() {}
Expand Down
2 changes: 1 addition & 1 deletion examples/bfetch_explorer/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["bfetch"],
"requiredPlugins": ["bfetch", "developerExamples"],
"optionalPlugins": []
}
25 changes: 22 additions & 3 deletions examples/bfetch_explorer/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@
* 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 { mount } from './mount';
import { DeveloperExamplesSetup } from '../../developer_examples/public';

export interface ExplorerService {
double: (number: { num: number }) => Promise<{ num: number }>;
}

export interface BfetchExplorerSetupPlugins {
bfetch: BfetchPublicSetup;
developerExamples: DeveloperExamplesSetup;
}

export interface BfetchExplorerStartPlugins {
Expand All @@ -36,9 +38,9 @@ export interface BfetchExplorerStartPlugins {
export class BfetchExplorerPlugin implements Plugin {
public setup(
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',
});

Expand All @@ -49,8 +51,25 @@ export class BfetchExplorerPlugin implements Plugin {
core.application.register({
id: 'bfetch-explorer',
title: 'bfetch explorer',
navLinkStatus: AppNavLinkStatus.hidden,
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() {}
Expand Down
36 changes: 36 additions & 0 deletions examples/developer_examples/README.md
Original file line number Diff line number Diff line change
@@ -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" />
9 changes: 9 additions & 0 deletions examples/developer_examples/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "developerExamples",
"version": "0.0.1",
"kibanaVersion": "kibana",
"server": false,
"ui": true,
"requiredPlugins": [],
"optionalPlugins": []
}
Binary file added examples/developer_examples/navigation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions examples/developer_examples/public/app.tsx
Original file line number Diff line number Diff line change
@@ -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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
* under the License.
*/

export * from '../../../../../examples/bfetch_explorer/public';
import { DeveloperExamplesPlugin } from './plugin';

export const plugin = () => new DeveloperExamplesPlugin();

export { DeveloperExamplesSetup } from './plugin';
68 changes: 68 additions & 0 deletions examples/developer_examples/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -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() {}
}
Loading

0 comments on commit f89e911

Please sign in to comment.