Skip to content

Commit

Permalink
Routing example plugin (#69581)
Browse files Browse the repository at this point in the history
* Routing example plugin

* Routing example plugin

* address review comments

* consolidate route registration into single function

* ts fix

* Add functional tests

* typescript fix

* Fix typo

* check against HttpFetchError not Error

* fix ts

* fix unhappy ci

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
stacey-gammon and elasticmachine authored Jul 15, 2020
1 parent badb7b5 commit 339e13b
Show file tree
Hide file tree
Showing 22 changed files with 1,107 additions and 0 deletions.
9 changes: 9 additions & 0 deletions examples/routing_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Team owner: Platform

A working example of a plugin that registers and uses multiple custom routes.

Read more:

- [IRouter API Docs](../../docs/development/core/server/kibana-plugin-core-server.irouter.md)
- [HttpHandler (core.http.fetch) API Docs](../../docs/development/core/public/kibana-plugin-core-public.httphandler.md)
- [Routing Conventions](../../STYLEGUIDE.md#api-endpoints)
27 changes: 27 additions & 0 deletions examples/routing_example/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.
*/

export const RANDOM_NUMBER_ROUTE_PATH = '/api/random_number';

export const RANDOM_NUMBER_BETWEEN_ROUTE_PATH = '/api/random_number_between';

export const POST_MESSAGE_ROUTE_PATH = '/api/post_message';

// Internal APIs should use the `internal` prefix, instead of the `api` prefix.
export const INTERNAL_GET_MESSAGE_BY_ID_ROUTE = '/internal/get_message';
9 changes: 9 additions & 0 deletions examples/routing_example/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "routingExample",
"version": "0.0.1",
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["developerExamples"],
"optionalPlugins": []
}
105 changes: 105 additions & 0 deletions examples/routing_example/public/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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 from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters } from 'kibana/public';
import {
EuiPage,
EuiPageBody,
EuiPageContent,
EuiText,
EuiHorizontalRule,
EuiPageContentHeader,
EuiListGroup,
} from '@elastic/eui';
import { RandomNumberRouteExample } from './random_number_example';
import { RandomNumberBetweenRouteExample } from './random_number_between_example';
import { Services } from './services';
import { PostMessageRouteExample } from './post_message_example';
import { GetMessageRouteExample } from './get_message_example';

type Props = Services;

function RoutingExplorer({
fetchRandomNumber,
fetchRandomNumberBetween,
addSuccessToast,
postMessage,
getMessageById,
}: Props) {
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<EuiPageContentHeader>
<EuiText>
<h1>Routing examples</h1>
</EuiText>
</EuiPageContentHeader>
<EuiText>
<EuiListGroup
listItems={[
{
label: 'IRouter API docs',
href:
'https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.irouter.md',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
{
label: 'HttpHandler (core.http.fetch) API docs',
href:
'https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.httphandler.md',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
{
label: 'Conventions',
href: 'https://github.com/elastic/kibana/tree/master/STYLEGUIDE.md#api-endpoints',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
]}
/>
</EuiText>
<EuiHorizontalRule />
<RandomNumberRouteExample fetchRandomNumber={fetchRandomNumber} />
<EuiHorizontalRule />
<RandomNumberBetweenRouteExample fetchRandomNumberBetween={fetchRandomNumberBetween} />

<EuiHorizontalRule />
<PostMessageRouteExample addSuccessToast={addSuccessToast} postMessage={postMessage} />

<EuiHorizontalRule />
<GetMessageRouteExample getMessageById={getMessageById} />
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}

export const renderApp = (props: Props, element: AppMountParameters['element']) => {
ReactDOM.render(<RoutingExplorer {...props} />, element);

return () => ReactDOM.unmountComponentAtNode(element);
};
96 changes: 96 additions & 0 deletions examples/routing_example/public/get_message_example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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, { useCallback } from 'react';
import { useState } from 'react';
import {
EuiText,
EuiButton,
EuiLoadingSpinner,
EuiFieldText,
EuiCallOut,
EuiFormRow,
} from '@elastic/eui';
import { HttpFetchError } from '../../../src/core/public';
import { isError } from './is_error';
import { Services } from './services';

interface Props {
getMessageById: Services['getMessageById'];
}

export function GetMessageRouteExample({ getMessageById }: Props) {
const [error, setError] = useState<HttpFetchError | undefined>();
const [isFetching, setIsFetching] = useState<boolean>(false);
const [message, setMessage] = useState<string>('');
const [id, setId] = useState<string>('');

const doFetch = useCallback(async () => {
if (isFetching) return;
setIsFetching(true);
const response = await getMessageById(id);

if (isError(response)) {
setError(response);
setMessage('');
} else {
setError(undefined);
setMessage(response);
}

setIsFetching(false);
}, [isFetching, getMessageById, setMessage, id]);

return (
<React.Fragment>
<EuiText>
<h2>GET example with param</h2>

<p>This examples uses a simple GET route that takes an id as a param in the route path.</p>
<EuiFormRow label="Message Id">
<EuiFieldText
value={id}
onChange={(e) => setId(e.target.value)}
data-test-subj="routingExampleGetMessageId"
/>
</EuiFormRow>

<EuiFormRow hasEmptyLabelSpace={true}>
<EuiButton
data-test-subj="routingExampleFetchMessage"
disabled={isFetching || id === ''}
onClick={() => doFetch()}
>
{isFetching ? <EuiLoadingSpinner /> : 'Get message'}
</EuiButton>
</EuiFormRow>

{error !== undefined ? (
<EuiCallOut color="danger" iconType="alert">
{error.message}
</EuiCallOut>
) : null}
{message !== '' ? (
<p>
Message is: <pre data-test-subj="routingExampleGetMessage">{message}</pre>
</p>
) : null}
</EuiText>
</React.Fragment>
);
}
23 changes: 23 additions & 0 deletions examples/routing_example/public/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { PluginInitializer } from 'kibana/public';
import { RoutingExamplePlugin } from './plugin';

export const plugin: PluginInitializer<{}, {}> = () => new RoutingExamplePlugin();
24 changes: 24 additions & 0 deletions examples/routing_example/public/is_error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { HttpFetchError } from '../../../src/core/public';

export function isError<T>(error: T | HttpFetchError): error is HttpFetchError {
return error instanceof HttpFetchError;
}
78 changes: 78 additions & 0 deletions examples/routing_example/public/plugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 {
CoreStart,
Plugin,
CoreSetup,
AppMountParameters,
AppNavLinkStatus,
} from '../../../src/core/public';
import { DeveloperExamplesSetup } from '../../developer_examples/public';
import { getServices } from './services';

interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
}

export class RoutingExamplePlugin implements Plugin<{}, {}, SetupDeps, {}> {
public setup(core: CoreSetup, { developerExamples }: SetupDeps) {
core.application.register({
id: 'routingExample',
title: 'Routing',
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) {
const [coreStart] = await core.getStartServices();
const startServices = getServices(coreStart);
const { renderApp } = await import('./app');
return renderApp(startServices, params.element);
},
});

developerExamples.register({
appId: 'routingExample',
title: 'Routing',
description: `Examples show how to use core routing and fetch services to register and query your own custom routes.`,
links: [
{
label: 'IRouter',
href:
'https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.irouter.md',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
{
label: 'HttpHandler (core.http.fetch)',
href:
'https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.httphandler.md',
iconType: 'logoGithub',
target: '_blank',
size: 's',
},
],
});
return {};
}

public start(core: CoreStart) {
return {};
}

public stop() {}
}
Loading

0 comments on commit 339e13b

Please sign in to comment.