Skip to content

Commit

Permalink
Enhancements pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
stacey-gammon committed Apr 8, 2020
1 parent d4f2bd7 commit f0d5bab
Show file tree
Hide file tree
Showing 20 changed files with 516 additions and 0 deletions.
27 changes: 27 additions & 0 deletions examples/enhancements_pattern_explorer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Enhancements pattern

This plugin shows a recommended pattern for how one plugin can enhance another plugins functionality when
dealing with a registry of items.

There are three key pieces. The plugin that creates the registry should:

- Expose a `setCustomProvider` function in the Setup contract.
- Expose the ability for other plugins to register _definitions_ in the setup contract.
- Expose the ability for other plugins to retrieve _instances_ in the start contract.

There are three plugin associated with this example.

- the `greeting` plugin exposes a registry of greetings. The default provider uses the very basic `alert` function to greet the user.
- the `greetingEnhanced` plugin registers a custom greeting provider which uses an EuiModal to greet the user with improved stylign.
- this plugin, `enhancementsPatternExplorer` registers a few example greetings as well as an app to expose the `greet` functionality.

To see how this works, first run Kibana with nothing in your `kibana.yml` via `yarn start --run-examples`. Navigate to the Enhancements pattern
app and see how the greetings look.

Then, stop kibana and edit `kibana.yml` to turn the `greetingEnhanced` plugin off by adding this line:

```
greeting_enhanced.enabled: false
```

Restart kibana and go through the same motions, and you should now see just the basic `alert` window.
10 changes: 10 additions & 0 deletions examples/enhancements_pattern_explorer/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "enhancedPatternExplorer",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["enhanced_pattern_explorer"],
"server": false,
"ui": true,
"requiredPlugins": ["greeting"],
"optionalPlugins": []
}
17 changes: 17 additions & 0 deletions examples/enhancements_pattern_explorer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "enhancements_pattern_explorer",
"version": "1.0.0",
"main": "target/examples/enhancements_pattern_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"
}
}
74 changes: 74 additions & 0 deletions examples/enhancements_pattern_explorer/public/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { EuiPageContent } from '@elastic/eui';
import { EuiFieldText } from '@elastic/eui';
import { EuiSelect } from '@elastic/eui';
import { AppMountParameters } from 'kibana/public';

import { EuiButton } from '@elastic/eui';
import { EuiText } from '@elastic/eui';
import { EuiCode } from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { Services } from './services';

function EnhancementsPatternApp(props: Services) {
const [name, setName] = useState('');
const [greeterId, setGreeterId] = useState('Casual');
return (
<EuiPageContent>
<EuiText>
<h1>Enhancements pattern</h1>
This explorer shows how one plugin can add enhancements via a{' '}
<EuiCode>setCustomProvider</EuiCode> pattern. If you run kibana with{' '}
<EuiCode>syarn start --run-examples</EuiCode> and click the Greet me button, you should see
a modal. This is the enhanced functionality. If you set{' '}
<EuiCode>greetingEnhanced.enabled: false</EuiCode> in your kibana.yml and then run this
example again you should only see a simple alert window, the unenhanced version.
</EuiText>
<EuiSpacer />
<EuiSelect
value={greeterId}
onChange={e => setGreeterId(e.target.value)}
options={props.getGreeterIds().map(id => ({
value: id,
text: id,
}))}
/>
<EuiFieldText
placeholder="What is your name?"
value={name}
onChange={e => setName(e.target.value)}
/>
<EuiButton
disabled={greeterId === '' || name === ''}
onClick={() => props.greetWithGreeter(greeterId, name)}
>
Greet me
</EuiButton>
</EuiPageContent>
);
}

export const renderApp = (services: Services, element: AppMountParameters['element']) => {
ReactDOM.render(<EnhancementsPatternApp {...services} />, element);

return () => ReactDOM.unmountComponentAtNode(element);
};
22 changes: 22 additions & 0 deletions examples/enhancements_pattern_explorer/public/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { EnhancedPatternExplorerPlugin } from './plugin';

export const plugin = () => new EnhancedPatternExplorerPlugin();
52 changes: 52 additions & 0 deletions examples/enhancements_pattern_explorer/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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, AppMountParameters, Plugin } from 'kibana/public';

import { GreetingStart, GreetingSetup } from 'examples/greeting/public';
import { getServices } from './services';

interface SetupDependencies {
greeting: GreetingSetup;
}

interface StartDependencies {
greeting: GreetingStart;
}

export class EnhancedPatternExplorerPlugin
implements Plugin<void, void, SetupDependencies, StartDependencies> {
setup(core: CoreSetup<StartDependencies>, { greeting }: SetupDependencies) {
greeting.registerGreeting({ id: 'Casual', salutation: 'Hey there', punctuation: '.' });
greeting.registerGreeting({ id: 'Excited', salutation: 'Hi', punctuation: '!!' });
greeting.registerGreeting({ id: 'Formal', salutation: 'Hello ', punctuation: '.' });

core.application.register({
id: 'enhancingmentsPattern',
title: 'Ennhancements pattern',
async mount(params: AppMountParameters) {
const { renderApp } = await import('./app');
const [, depsStart] = await core.getStartServices();
return renderApp(getServices({ greetingServices: depsStart.greeting }), params.element);
},
});
}

start() {}
}
44 changes: 44 additions & 0 deletions examples/enhancements_pattern_explorer/public/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { GreetingStart } from '../../greeting/public';

export interface Services {
greetWithGreeter: (id: string, name: string) => void;
getGreeterIds: () => string[];
}

/**
* Rather than pass down depencies directly, we add some indirection with this services file, to help decouple and
* buffer this plugin from any changes in dependency contracts.
* @param dependencies
*/
export const getServices = (dependencies: { greetingServices: GreetingStart }): Services => ({
greetWithGreeter: (greeterId: string, name: string) => {
const greeting = dependencies.greetingServices.getGreeting(greeterId);

if (!greeting) {
throw new Error(`No Greeter registered with id ${greeterId}`);
}

greeting.greetMe(name);
},

getGreeterIds: () => dependencies.greetingServices.getRegisteredGreetings(),
});
14 changes: 14 additions & 0 deletions examples/enhancements_pattern_explorer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"../../typings/**/*",
],
"exclude": []
}
4 changes: 4 additions & 0 deletions examples/greeting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Greeting

This is a very simple plugin that exposes a registry of greetings. Its used to highlight a
pattern for enhancing basic implementations. Embeddables uses this pattern, drilldown functionality enhances it.
10 changes: 10 additions & 0 deletions examples/greeting/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "greeting",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["greeting"],
"server": false,
"ui": true,
"requiredPlugins": [],
"optionalPlugins": []
}
17 changes: 17 additions & 0 deletions examples/greeting/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "greeting",
"version": "1.0.0",
"main": "target/examples/greeting",
"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"
}
}
23 changes: 23 additions & 0 deletions examples/greeting/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 { GreetingPlugin } from './plugin';

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

export { GreetingStart, GreetingSetup, Greeting, GreetingDefinition } from './plugin';
70 changes: 70 additions & 0 deletions examples/greeting/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 { Plugin } from '../../../src/core/public';

export interface GreetingDefinition {
id: string;
salutation: string;
punctuation: string;
}

export interface Greeting {
greetMe: (name: string) => void;
}

type GreetingProvider = (def: GreetingDefinition) => Greeting;

const defaultGreetingProvider: GreetingProvider = (def: GreetingDefinition) => ({
greetMe: (name: string) => alert(`${def.salutation} ${name}${def.punctuation}`),
});

export interface GreetingStart {
getGreeting: (id: string) => Greeting;
getRegisteredGreetings: () => string[];
}

export interface GreetingSetup {
setCustomProvider: (customProvider: GreetingProvider) => void;
registerGreeting: (greetingDefinition: GreetingDefinition) => void;
}

export class GreetingPlugin implements Plugin<GreetingSetup, GreetingStart> {
private greetings: { [key: string]: Greeting } = {};
private greetingDefinitions: { [key: string]: GreetingDefinition } = {};
private greetingProvider: GreetingProvider = defaultGreetingProvider;

setup = () => ({
setCustomProvider: (customProvider: GreetingProvider) =>
(this.greetingProvider = customProvider),
registerGreeting: (greetingDefinition: GreetingDefinition) =>
(this.greetingDefinitions[greetingDefinition.id] = greetingDefinition),
});

start() {
Object.values(this.greetingDefinitions).forEach(
greetingDefinition =>
(this.greetings[greetingDefinition.id] = this.greetingProvider(greetingDefinition))
);
return {
getGreeting: (id: string) => this.greetings[id],
getRegisteredGreetings: () => Object.keys(this.greetings),
};
}
}
Loading

0 comments on commit f0d5bab

Please sign in to comment.