diff --git a/examples/custom_greetings/README.md b/examples/custom_greetings/README.md new file mode 100644 index 0000000000000..91eabd11e015f --- /dev/null +++ b/examples/custom_greetings/README.md @@ -0,0 +1,23 @@ +## Custom Greetings + +This plugin is an example of adding custom implementations to a registry supplied by another plugin. + +It follows the best practice of also emitting direct accessors to these implementations on this +plugin's start contract. + +This +```ts + const casualGreeter = customGreetings.getCasualGreeter(); +``` + +should be preferred to + +```ts + const casualGreeter = greeting.getGreeter('CASUAL_GREETER'); +``` + +becuase: + - the accessing plugin doesn't need to handle the possibility of `casualGreeter` being undefined + - it's more obvious that the plugin accessing this greeter should list `customGreetings` as a plugin dependency + - if the specific implementation had a specialized type, it can be accessed this way, in lieu of supporting + typescript generics on the generic getter (e.g. `greeting.getGreeter(id)`), which is error prone. \ No newline at end of file diff --git a/examples/custom_greetings/kibana.json b/examples/custom_greetings/kibana.json new file mode 100644 index 0000000000000..a43ad299ce5e2 --- /dev/null +++ b/examples/custom_greetings/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "customGreetings", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["custom_greetings"], + "server": false, + "ui": true, + "requiredPlugins": ["greeting"], + "optionalPlugins": [] +} diff --git a/examples/custom_greetings/package.json b/examples/custom_greetings/package.json new file mode 100644 index 0000000000000..03ea6d9211fd0 --- /dev/null +++ b/examples/custom_greetings/package.json @@ -0,0 +1,17 @@ +{ + "name": "custom_greetings", + "version": "1.0.0", + "main": "target/examples/custom_greetings", + "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" + } +} diff --git a/examples/custom_greetings/public/index.ts b/examples/custom_greetings/public/index.ts new file mode 100644 index 0000000000000..b79acd8c79132 --- /dev/null +++ b/examples/custom_greetings/public/index.ts @@ -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 { CustomGreetingsPlugin } from './plugin'; + +export const plugin = () => new CustomGreetingsPlugin(); + +export { CustomGreetingsStart } from './plugin'; diff --git a/examples/custom_greetings/public/plugin.ts b/examples/custom_greetings/public/plugin.ts new file mode 100644 index 0000000000000..492a49c15ad28 --- /dev/null +++ b/examples/custom_greetings/public/plugin.ts @@ -0,0 +1,79 @@ +/* + * 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 } from 'kibana/public'; + +import { GreetingStart, GreetingSetup, Greeting } from 'examples/greeting/public'; + +interface SetupDependencies { + greeting: GreetingSetup; +} + +interface StartDependencies { + greeting: GreetingStart; +} + +/** + * Expose direct access to specific greeter implementations on the start contract. + * If a plugin knows ahead of time which specific implementation they would like to access + * they should access it directly off this plugin as opposed to retrieving it off the + * generic registry, via `greeting.getGreeter('Casual')`. + */ +export interface CustomGreetingsStart { + getCasualGreeter: () => Greeting; + getExcitedGreeter: () => Greeting; + getFormalGreeter: () => Greeting; +} + +export class CustomGreetingsPlugin + implements Plugin { + private casualGreeterProvider?: () => Greeting; + private excitedGreeterProvider?: () => Greeting; + private formalGreeterProvider?: () => Greeting; + + setup(core: CoreSetup, { greeting }: SetupDependencies) { + this.casualGreeterProvider = greeting.registerGreetingDefinition({ + id: 'Casual', + salutation: 'Hey there', + punctuation: '.', + }); + this.excitedGreeterProvider = greeting.registerGreetingDefinition({ + id: 'Excited', + salutation: 'Hi', + punctuation: '!!', + }); + this.formalGreeterProvider = greeting.registerGreetingDefinition({ + id: 'Formal', + salutation: 'Hello ', + punctuation: '.', + }); + } + + start() { + const { casualGreeterProvider, excitedGreeterProvider, formalGreeterProvider } = this; + if (!casualGreeterProvider || !excitedGreeterProvider || !formalGreeterProvider) { + throw new Error('Something unexpected went wrong. Greeters should be defined by now.'); + } + return { + getCasualGreeter: () => casualGreeterProvider(), + getExcitedGreeter: () => excitedGreeterProvider(), + getFormalGreeter: () => formalGreeterProvider(), + }; + } +} diff --git a/examples/custom_greetings/public/services.ts b/examples/custom_greetings/public/services.ts new file mode 100644 index 0000000000000..c62a9372c9efb --- /dev/null +++ b/examples/custom_greetings/public/services.ts @@ -0,0 +1,35 @@ +/* + * 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, Greeting } from '../../greeting/public'; + +export interface Services { + greetWithGreeter: (greeting: Greeting, name: string) => void; + getGreeters: () => Greeting[]; +} + +/** + * 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: (greeting: Greeting, name: string) => greeting.greetMe(name), + getGreeters: () => dependencies.greetingServices.getGreeters(), +}); diff --git a/examples/custom_greetings/tsconfig.json b/examples/custom_greetings/tsconfig.json new file mode 100644 index 0000000000000..199fbe1fcfa26 --- /dev/null +++ b/examples/custom_greetings/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [] +} diff --git a/examples/enhancements_pattern_explorer/README.md b/examples/enhancements_pattern_explorer/README.md new file mode 100644 index 0000000000000..babc3949f5aa0 --- /dev/null +++ b/examples/enhancements_pattern_explorer/README.md @@ -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. \ No newline at end of file diff --git a/examples/enhancements_pattern_explorer/kibana.json b/examples/enhancements_pattern_explorer/kibana.json new file mode 100644 index 0000000000000..e4415ed5bd161 --- /dev/null +++ b/examples/enhancements_pattern_explorer/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "enhancedPatternExplorer", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["enhanced_pattern_explorer"], + "server": false, + "ui": true, + "requiredPlugins": ["greeting", "customGreetings"], + "optionalPlugins": [] +} diff --git a/examples/enhancements_pattern_explorer/package.json b/examples/enhancements_pattern_explorer/package.json new file mode 100644 index 0000000000000..332aaf80d657c --- /dev/null +++ b/examples/enhancements_pattern_explorer/package.json @@ -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" + } +} diff --git a/examples/enhancements_pattern_explorer/public/app.tsx b/examples/enhancements_pattern_explorer/public/app.tsx new file mode 100644 index 0000000000000..268714c0ad7c2 --- /dev/null +++ b/examples/enhancements_pattern_explorer/public/app.tsx @@ -0,0 +1,125 @@ +/* + * 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 { EuiComboBox } 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 { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiFormRow } from '@elastic/eui'; +import { Services } from './services'; +import { Greeting } from '../../greeting/public'; + +function greeterToComboOption(greeter: Greeting) { + return { + value: greeter, + label: greeter.label, + }; +} + +function EnhancementsPatternApp(props: Services) { + const [name, setName] = useState(''); + const greetersAsOptions = props.getGreeters().map(greeter => greeterToComboOption(greeter)); + const defaultGreeting = props.getGreeters()[0]; + + const [selectedGreeter, setSelectedGreeter] = useState(defaultGreeting); + return ( + + +

Enhancements pattern

+ This explorer shows how one plugin can add enhancements via a{' '} + setCustomProvider pattern. If you run kibana with{' '} + yarn start --run-examples and click the Greet me button, you should see a + modal. This is the enhanced functionality. If you set{' '} + greetingEnhanced.enabled: false in your kibana.yml and then run this + example again you should only see a simple alert window, the unenhanced version. +
+ + + setName(e.target.value)} + /> + + + + + + + + selectedOptions={ + selectedGreeter ? [greeterToComboOption(selectedGreeter)] : undefined + } + onChange={e => { + setSelectedGreeter(e[0] ? e[0].value : undefined); + }} + options={greetersAsOptions} + singleSelection={{ asPlainText: true }} + /> + { + if (selectedGreeter) { + props.greetWithGreeter(selectedGreeter, name); + } + }} + > + Greet me + + + + + + + props.getCasualGreeter().greetMe(name)} + > + Greet me casually + + + + + + + + +
+ ); +} + +export const renderApp = (services: Services, element: AppMountParameters['element']) => { + ReactDOM.render(, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/enhancements_pattern_explorer/public/index.ts b/examples/enhancements_pattern_explorer/public/index.ts new file mode 100644 index 0000000000000..15cc8e169bb52 --- /dev/null +++ b/examples/enhancements_pattern_explorer/public/index.ts @@ -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(); diff --git a/examples/enhancements_pattern_explorer/public/plugin.ts b/examples/enhancements_pattern_explorer/public/plugin.ts new file mode 100644 index 0000000000000..7343e48513704 --- /dev/null +++ b/examples/enhancements_pattern_explorer/public/plugin.ts @@ -0,0 +1,51 @@ +/* + * 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 } from 'examples/greeting/public'; +import { CustomGreetingsStart } from 'examples/custom_greetings/public'; +import { getServices } from './services'; + +interface StartDependencies { + greeting: GreetingStart; + customGreetings: CustomGreetingsStart; +} + +export class EnhancedPatternExplorerPlugin implements Plugin { + setup(core: CoreSetup) { + 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({ + greeting: depsStart.greeting, + customGreetings: depsStart.customGreetings, + }), + params.element + ); + }, + }); + } + + start() {} +} diff --git a/examples/enhancements_pattern_explorer/public/services.ts b/examples/enhancements_pattern_explorer/public/services.ts new file mode 100644 index 0000000000000..a39a876a50c78 --- /dev/null +++ b/examples/enhancements_pattern_explorer/public/services.ts @@ -0,0 +1,41 @@ +/* + * 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 { CustomGreetingsStart } from 'examples/custom_greetings/public'; +import { GreetingStart, Greeting } from '../../greeting/public'; + +export interface Services { + greetWithGreeter: (greeting: Greeting, name: string) => void; + getGreeters: () => Greeting[]; + getCasualGreeter: () => Greeting; +} + +/** + * 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: { + greeting: GreetingStart; + customGreetings: CustomGreetingsStart; +}): Services => ({ + greetWithGreeter: (greeting: Greeting, name: string) => greeting.greetMe(name), + getGreeters: () => dependencies.greeting.getGreeters(), + getCasualGreeter: () => dependencies.customGreetings.getCasualGreeter(), +}); diff --git a/examples/enhancements_pattern_explorer/tsconfig.json b/examples/enhancements_pattern_explorer/tsconfig.json new file mode 100644 index 0000000000000..199fbe1fcfa26 --- /dev/null +++ b/examples/enhancements_pattern_explorer/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [] +} diff --git a/examples/greeting/README.md b/examples/greeting/README.md new file mode 100644 index 0000000000000..205e76642a8db --- /dev/null +++ b/examples/greeting/README.md @@ -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. \ No newline at end of file diff --git a/examples/greeting/kibana.json b/examples/greeting/kibana.json new file mode 100644 index 0000000000000..f1e0252ef1ebf --- /dev/null +++ b/examples/greeting/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "greeting", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["greeting"], + "server": false, + "ui": true, + "requiredPlugins": [], + "optionalPlugins": [] +} diff --git a/examples/greeting/package.json b/examples/greeting/package.json new file mode 100644 index 0000000000000..51953d87bf259 --- /dev/null +++ b/examples/greeting/package.json @@ -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" + } +} diff --git a/examples/greeting/public/index.ts b/examples/greeting/public/index.ts new file mode 100644 index 0000000000000..9ae4d1f4e06e9 --- /dev/null +++ b/examples/greeting/public/index.ts @@ -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, Greeter as Greeting, GreetingDefinition } from './plugin'; diff --git a/examples/greeting/public/plugin.ts b/examples/greeting/public/plugin.ts new file mode 100644 index 0000000000000..9b90e6418197f --- /dev/null +++ b/examples/greeting/public/plugin.ts @@ -0,0 +1,118 @@ +/* + * 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 Greeter { + greetMe: (name: string) => void; + label: string; +} + +type GreetingProvider = (def: GreetingDefinition) => Greeter; + +const defaultGreetingProvider: GreetingProvider = (def: GreetingDefinition) => ({ + greetMe: (name: string) => alert(`${def.salutation} ${name}${def.punctuation}`), + label: def.id, +}); + +export interface GreetingStart { + /** + * This function should be used if the value of `id` is not known at compile time. Usually + * this is because `id` has been persisted somewhere. If the value of `id` is known at + * compile time (e.g. `greeting.getGreeter(CASUAL_GREETER))`) developers should prefer accessing + * off the plugin that registered `CasualGreeter`, like `customGreetings.getCasualGreeter()`, because: + * - makes it more explicit you should add `customGreetings` to your plugin dependency list. + * - don't need to handle the possibility of `undefined` if it's a required dependency. + * - can get more specialized types in certain situations (e.g. `interface CustomGreeter` vs `Greeter` - + * does not apply to this example but many real world examples are like this.) + */ + getGreeter: (id: string) => Greeter; + /** + * Returns an array of all registered greeters. + */ + getGreeters: () => Greeter[]; +} + +export interface GreetingSetup { + /** + * Allows for another plugin to add a custom provider. Only one plugin is allowed to set a custom + * provider, any secondary attempts will result in an error. + */ + setCustomProvider: (customProvider: GreetingProvider) => void; + + /** + * Returns a greeting accessor that should only be used after the setup lifecycle completes. + */ + registerGreetingDefinition: (greetingDefinition: GreetingDefinition) => () => Greeter; +} + +export class GreetingPlugin implements Plugin { + private greetingDefinitions: { [key: string]: GreetingDefinition } = {}; + /** + * Greeting provider is set at the beginning of this plugins start lifecycle to ensure + * any other plugins had a chance to set a custom provider. + */ + private greetingProvider?: GreetingProvider; + + /** + * Store a custom provider set by another plugin. + */ + private customGreetingProvider?: GreetingProvider; + + setup = () => ({ + /** + * TODO: this only allows for a single greeting provider to be set. It would be nice + * to come up with a pattern that allowed for multiple custom providers to be registered. + */ + setCustomProvider: (customProvider: GreetingProvider) => { + if (this.customGreetingProvider) { + throw new Error( + 'Only one custom greeting provider is allowed, and one has already been registered.' + ); + } else { + this.customGreetingProvider = customProvider; + } + }, + registerGreetingDefinition: (greetingDefinition: GreetingDefinition) => { + this.greetingDefinitions[greetingDefinition.id] = greetingDefinition; + return () => { + if (!this.greetingProvider) { + throw new Error('Greeters can only be retrieved after setup lifecycle.'); + } else { + return this.greetingProvider(greetingDefinition); + } + }; + }, + }); + + start() { + this.greetingProvider = this.customGreetingProvider || defaultGreetingProvider; + return { + getGreeter: (id: string) => this.greetingProvider!(this.greetingDefinitions[id]), + + getGreeters: () => Object.values(this.greetingDefinitions).map(this.greetingProvider!), + }; + } +} diff --git a/examples/greeting/tsconfig.json b/examples/greeting/tsconfig.json new file mode 100644 index 0000000000000..199fbe1fcfa26 --- /dev/null +++ b/examples/greeting/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [] +} diff --git a/examples/greeting_enhanced/README.md b/examples/greeting_enhanced/README.md new file mode 100644 index 0000000000000..e6afd864802de --- /dev/null +++ b/examples/greeting_enhanced/README.md @@ -0,0 +1,3 @@ +## Greeting Enhanced + +This plugin shows one pattern how one plugin can enhance another plugins functionality. \ No newline at end of file diff --git a/examples/greeting_enhanced/kibana.json b/examples/greeting_enhanced/kibana.json new file mode 100644 index 0000000000000..08c1ba1d5393e --- /dev/null +++ b/examples/greeting_enhanced/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "greetingEnhanced", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["greeting_enhanced"], + "server": false, + "ui": true, + "requiredPlugins": ["greeting"], + "optionalPlugins": [] +} diff --git a/examples/greeting_enhanced/package.json b/examples/greeting_enhanced/package.json new file mode 100644 index 0000000000000..d4847609cc5d4 --- /dev/null +++ b/examples/greeting_enhanced/package.json @@ -0,0 +1,17 @@ +{ + "name": "greeting_enhanced", + "version": "1.0.0", + "main": "target/examples/greeting_enhanced", + "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" + } +} diff --git a/examples/greeting_enhanced/public/index.ts b/examples/greeting_enhanced/public/index.ts new file mode 100644 index 0000000000000..a77ea3cab4c8a --- /dev/null +++ b/examples/greeting_enhanced/public/index.ts @@ -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 { GreetingEnhancedPlugin } from './plugin'; + +export const plugin = () => new GreetingEnhancedPlugin(); diff --git a/examples/greeting_enhanced/public/plugin.tsx b/examples/greeting_enhanced/public/plugin.tsx new file mode 100644 index 0000000000000..92401db39a037 --- /dev/null +++ b/examples/greeting_enhanced/public/plugin.tsx @@ -0,0 +1,53 @@ +/* + * 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 { EuiModalBody } from '@elastic/eui'; + +import { toMountPoint } from '../../../src/plugins/kibana_react/public'; +import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; +import { GreetingSetup, GreetingDefinition } from '../../greeting/public'; + +interface SetupDependencies { + greeting: GreetingSetup; +} + +export class GreetingEnhancedPlugin implements Plugin { + private startServices?: { overlays: CoreStart['overlays'] }; + + setup(core: CoreSetup, { greeting }: SetupDependencies) { + greeting.setCustomProvider((def: GreetingDefinition) => ({ + greetMe: (name: string) => + this.getStartServices().overlays.openModal( + toMountPoint({`${def.salutation} ${name}${def.punctuation}`}) + ), + label: def.id, + })); + } + + start(core: CoreStart) { + this.startServices = { overlays: core.overlays }; + } + + getStartServices() { + if (!this.startServices) { + throw new Error('Accessing start services before they have been initialized'); + } + return this.startServices; + } +} diff --git a/examples/greeting_enhanced/tsconfig.json b/examples/greeting_enhanced/tsconfig.json new file mode 100644 index 0000000000000..199fbe1fcfa26 --- /dev/null +++ b/examples/greeting_enhanced/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [] +}