Skip to content

Commit

Permalink
extract greeting implementations into separate plugin to showcase acc…
Browse files Browse the repository at this point in the history
…essing statically
  • Loading branch information
stacey-gammon committed Apr 10, 2020
1 parent 6b55c41 commit 41f1cc9
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 78 deletions.
23 changes: 23 additions & 0 deletions examples/custom_greetings/README.md
Original file line number Diff line number Diff line change
@@ -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<T>(id)`), which is error prone.
10 changes: 10 additions & 0 deletions examples/custom_greetings/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "customGreetings",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["custom_greetings"],
"server": false,
"ui": true,
"requiredPlugins": ["greeting"],
"optionalPlugins": []
}
17 changes: 17 additions & 0 deletions examples/custom_greetings/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
24 changes: 24 additions & 0 deletions examples/custom_greetings/public/index.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 { CustomGreetingsPlugin } from './plugin';

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

export { CustomGreetingsStart } from './plugin';
79 changes: 79 additions & 0 deletions examples/custom_greetings/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -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<void, CustomGreetingsStart, SetupDependencies, StartDependencies> {
private casualGreeterProvider?: () => Greeting;
private excitedGreeterProvider?: () => Greeting;
private formalGreeterProvider?: () => Greeting;

setup(core: CoreSetup<StartDependencies>, { 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: () => casualGreeterProvider(),
getFormalGreeter: () => casualGreeterProvider(),
};
}
}
35 changes: 35 additions & 0 deletions examples/custom_greetings/public/services.ts
Original file line number Diff line number Diff line change
@@ -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(),
});
14 changes: 14 additions & 0 deletions examples/custom_greetings/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": []
}
2 changes: 1 addition & 1 deletion examples/enhancements_pattern_explorer/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"configPath": ["enhanced_pattern_explorer"],
"server": false,
"ui": true,
"requiredPlugins": ["greeting"],
"requiredPlugins": ["greeting", "customGreetings"],
"optionalPlugins": []
}
86 changes: 61 additions & 25 deletions examples/enhancements_pattern_explorer/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ 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';

Expand All @@ -39,8 +42,8 @@ function greeterToComboOption(greeter: Greeting) {

function EnhancementsPatternApp(props: Services) {
const [name, setName] = useState('');
const greetersAsOptions = props.getGreeterObjects().map(greeter => greeterToComboOption(greeter));
const defaultGreeting = props.getGreeterObjects()[0];
const greetersAsOptions = props.getGreeters().map(greeter => greeterToComboOption(greeter));
const defaultGreeting = props.getGreeters()[0];

const [selectedGreeter, setSelectedGreeter] = useState<Greeting | undefined>(defaultGreeting);
return (
Expand All @@ -55,29 +58,62 @@ function EnhancementsPatternApp(props: Services) {
example again you should only see a simple alert window, the unenhanced version.
</EuiText>
<EuiSpacer />
<EuiComboBox<Greeting>
selectedOptions={selectedGreeter ? [greeterToComboOption(selectedGreeter)] : undefined}
onChange={e => {
setSelectedGreeter(e[0] ? e[0].value : undefined);
}}
options={greetersAsOptions}
singleSelection={{ asPlainText: true }}
/>
<EuiFieldText
placeholder="What is your name?"
value={name}
onChange={e => setName(e.target.value)}
/>
<EuiButton
disabled={selectedGreeter === undefined || name === ''}
onClick={() => {
if (selectedGreeter) {
props.greetWithGreeter(selectedGreeter, name);
}
}}
>
Greet me
</EuiButton>
<EuiFormRow>
<EuiFieldText
placeholder="What is your name?"
value={name}
onChange={e => setName(e.target.value)}
/>
</EuiFormRow>
<EuiSpacer size="l" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
helpText="This functionality will use a greeter known only at runtime, so
all these greeters are accessed off the generic registry plugin."
>
<EuiFlexItem>
<EuiComboBox<Greeting>
selectedOptions={
selectedGreeter ? [greeterToComboOption(selectedGreeter)] : undefined
}
onChange={e => {
setSelectedGreeter(e[0] ? e[0].value : undefined);
}}
options={greetersAsOptions}
singleSelection={{ asPlainText: true }}
/>
<EuiButton
disabled={selectedGreeter === undefined || name === ''}
onClick={() => {
if (selectedGreeter) {
props.greetWithGreeter(selectedGreeter, name);
}
}}
>
Greet me
</EuiButton>
</EuiFlexItem>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
helpText="This button uses a greeter type known at compile time. Prefer accessing the
implementation directly off the plugin instead of accessing off the generic registry if possible."
>
<EuiButton
disabled={selectedGreeter === undefined || name === ''}
onClick={() => props.getCasualGreeter().greetMe(name)}
>
Greet me casually
</EuiButton>
</EuiFormRow>
</EuiFlexItem>

<EuiFlexItem>
<EuiSpacer size="l" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
);
}
Expand Down
49 changes: 13 additions & 36 deletions examples/enhancements_pattern_explorer/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,33 @@

import { CoreSetup, AppMountParameters, Plugin } from 'kibana/public';

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

interface SetupDependencies {
greeting: GreetingSetup;
}

interface StartDependencies {
greeting: GreetingStart;
customGreetings: CustomGreetingsStart;
}

interface EnhancedPatternExplorerPluginStart {
enhancedFirstGreeting: (name: string) => void;
}

export class EnhancedPatternExplorerPlugin
implements
Plugin<void, EnhancedPatternExplorerPluginStart, SetupDependencies, StartDependencies> {
firstGreeting?: () => Greeting;

setup(core: CoreSetup<StartDependencies>, { greeting }: SetupDependencies) {
this.firstGreeting = greeting.registerGreetingDefinition({
id: 'Casual',
salutation: 'Hey there',
punctuation: '.',
});
greeting.registerGreetingDefinition({ id: 'Excited', salutation: 'Hi', punctuation: '!!' });
greeting.registerGreetingDefinition({ id: 'Formal', salutation: 'Hello ', punctuation: '.' });

export class EnhancedPatternExplorerPlugin implements Plugin<void, void, {}, StartDependencies> {
setup(core: CoreSetup<StartDependencies>) {
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);
return renderApp(
getServices({
greeting: depsStart.greeting,
customGreetings: depsStart.customGreetings,
}),
params.element
);
},
});
}

start() {
// an example of registering a greeting and returning a reference to the
// plain or enhanced result
setTimeout(() => this.firstGreeting && this.firstGreeting().greetMe('code ninja'), 2000);
return {
enhancedFirstGreeting: (name: string) => {
if (this.firstGreeting) {
this.firstGreeting().greetMe(name);
}
},
};
}
start() {}
}
Loading

0 comments on commit 41f1cc9

Please sign in to comment.