mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Ui Actions explorer example (#57006)
* wip * Move action registration out of AppMountContext fn * Move all registration to setup * Fix type error
This commit is contained in:
parent
62e3189c34
commit
2ae70d9592
21 changed files with 929 additions and 1 deletions
8
examples/ui_action_examples/README.md
Normal file
8
examples/ui_action_examples/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Ui actions examples
|
||||
|
||||
These ui actions examples shows how to:
|
||||
- Register new actions
|
||||
- Register custom triggers
|
||||
- Attach an action to a trigger
|
||||
|
||||
To run this example, use the command `yarn start --run-examples`.
|
10
examples/ui_action_examples/kibana.json
Normal file
10
examples/ui_action_examples/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "uiActionsExamples",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["ui_actions_examples"],
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["uiActions"],
|
||||
"optionalPlugins": []
|
||||
}
|
17
examples/ui_action_examples/package.json
Normal file
17
examples/ui_action_examples/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "ui_actions_examples",
|
||||
"version": "1.0.0",
|
||||
"main": "target/examples/ui_actions_examples",
|
||||
"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.5.3"
|
||||
}
|
||||
}
|
43
examples/ui_action_examples/public/hello_world_action.tsx
Normal file
43
examples/ui_action_examples/public/hello_world_action.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { EuiText, EuiModalBody, EuiButton } from '@elastic/eui';
|
||||
import { OverlayStart } from '../../../src/core/public';
|
||||
import { createAction } from '../../../src/plugins/ui_actions/public';
|
||||
import { toMountPoint } from '../../../src/plugins/kibana_react/public';
|
||||
|
||||
export const HELLO_WORLD_ACTION_TYPE = 'HELLO_WORLD_ACTION_TYPE';
|
||||
|
||||
export const createHelloWorldAction = (openModal: OverlayStart['openModal']) =>
|
||||
createAction<{}>({
|
||||
type: HELLO_WORLD_ACTION_TYPE,
|
||||
getDisplayName: () => 'Hello World!',
|
||||
execute: async () => {
|
||||
const overlay = openModal(
|
||||
toMountPoint(
|
||||
<EuiModalBody>
|
||||
<EuiText data-test-subj="helloWorldActionText">Hello world!</EuiText>
|
||||
<EuiButton data-test-subj="closeModal" onClick={() => overlay.close()}>
|
||||
Close
|
||||
</EuiButton>
|
||||
</EuiModalBody>
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
28
examples/ui_action_examples/public/hello_world_trigger.ts
Normal file
28
examples/ui_action_examples/public/hello_world_trigger.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { Trigger } from '../../../src/plugins/ui_actions/public';
|
||||
import { HELLO_WORLD_ACTION_TYPE } from './hello_world_action';
|
||||
|
||||
export const HELLO_WORLD_TRIGGER_ID = 'HELLO_WORLD_TRIGGER_ID';
|
||||
|
||||
export const helloWorldTrigger: Trigger = {
|
||||
id: HELLO_WORLD_TRIGGER_ID,
|
||||
actionIds: [HELLO_WORLD_ACTION_TYPE],
|
||||
};
|
26
examples/ui_action_examples/public/index.ts
Normal file
26
examples/ui_action_examples/public/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { UiActionExamplesPlugin } from './plugin';
|
||||
import { PluginInitializer } from '../../../src/core/public';
|
||||
|
||||
export const plugin: PluginInitializer<void, void> = () => new UiActionExamplesPlugin();
|
||||
|
||||
export { HELLO_WORLD_TRIGGER_ID } from './hello_world_trigger';
|
||||
export { HELLO_WORLD_ACTION_TYPE } from './hello_world_action';
|
45
examples/ui_action_examples/public/plugin.ts
Normal file
45
examples/ui_action_examples/public/plugin.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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, CoreSetup, CoreStart } from '../../../src/core/public';
|
||||
import { UiActionsSetup, UiActionsStart } from '../../../src/plugins/ui_actions/public';
|
||||
import { createHelloWorldAction } from './hello_world_action';
|
||||
import { helloWorldTrigger } from './hello_world_trigger';
|
||||
|
||||
interface UiActionExamplesSetupDependencies {
|
||||
uiActions: UiActionsSetup;
|
||||
}
|
||||
|
||||
interface UiActionExamplesStartDependencies {
|
||||
uiActions: UiActionsStart;
|
||||
}
|
||||
|
||||
export class UiActionExamplesPlugin
|
||||
implements
|
||||
Plugin<void, void, UiActionExamplesSetupDependencies, UiActionExamplesStartDependencies> {
|
||||
public setup(core: CoreSetup, deps: UiActionExamplesSetupDependencies) {
|
||||
deps.uiActions.registerTrigger(helloWorldTrigger);
|
||||
}
|
||||
|
||||
public start(coreStart: CoreStart, deps: UiActionExamplesStartDependencies) {
|
||||
deps.uiActions.registerAction(createHelloWorldAction(coreStart.overlays.openModal));
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
15
examples/ui_action_examples/tsconfig.json
Normal file
15
examples/ui_action_examples/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../typings/**/*",
|
||||
],
|
||||
"exclude": []
|
||||
}
|
8
examples/ui_actions_explorer/README.md
Normal file
8
examples/ui_actions_explorer/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Ui actions explorer
|
||||
|
||||
This example ui actions explorer app shows how to:
|
||||
- Add custom ui actions to existing triggers
|
||||
- Add custom triggers
|
||||
|
||||
|
||||
To run this example, use the command `yarn start --run-examples`.
|
10
examples/ui_actions_explorer/kibana.json
Normal file
10
examples/ui_actions_explorer/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "uiActionsExplorer",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["ui_actions_explorer"],
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["uiActions", "uiActionsExamples"],
|
||||
"optionalPlugins": []
|
||||
}
|
17
examples/ui_actions_explorer/package.json
Normal file
17
examples/ui_actions_explorer/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "ui_actions_explorer",
|
||||
"version": "1.0.0",
|
||||
"main": "target/examples/ui_actions_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.5.3"
|
||||
}
|
||||
}
|
131
examples/ui_actions_explorer/public/actions/actions.tsx
Normal file
131
examples/ui_actions_explorer/public/actions/actions.tsx
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 { OverlayStart } from 'kibana/public';
|
||||
import { EuiFieldText, EuiModalBody, EuiButton } from '@elastic/eui';
|
||||
import { useState } from 'react';
|
||||
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
|
||||
import { createAction, UiActionsStart } from '../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export const USER_TRIGGER = 'USER_TRIGGER';
|
||||
export const COUNTRY_TRIGGER = 'COUNTRY_TRIGGER';
|
||||
export const PHONE_TRIGGER = 'PHONE_TRIGGER';
|
||||
|
||||
export const VIEW_IN_MAPS_ACTION = 'VIEW_IN_MAPS_ACTION';
|
||||
export const TRAVEL_GUIDE_ACTION = 'TRAVEL_GUIDE_ACTION';
|
||||
export const CALL_PHONE_NUMBER_ACTION = 'CALL_PHONE_NUMBER_ACTION';
|
||||
export const EDIT_USER_ACTION = 'EDIT_USER_ACTION';
|
||||
export const PHONE_USER_ACTION = 'PHONE_USER_ACTION';
|
||||
export const SHOWCASE_PLUGGABILITY_ACTION = 'SHOWCASE_PLUGGABILITY_ACTION';
|
||||
|
||||
export const showcasePluggability = createAction<{}>({
|
||||
type: SHOWCASE_PLUGGABILITY_ACTION,
|
||||
getDisplayName: () => 'This is pluggable! Any plugin can inject their actions here.',
|
||||
execute: async ({}) => alert("Isn't that cool?!"),
|
||||
});
|
||||
|
||||
export const makePhoneCallAction = createAction<{ phone: string }>({
|
||||
type: CALL_PHONE_NUMBER_ACTION,
|
||||
getDisplayName: () => 'Call phone number',
|
||||
execute: async ({ phone }) => alert(`Pretend calling ${phone}...`),
|
||||
});
|
||||
|
||||
export const lookUpWeatherAction = createAction<{ country: string }>({
|
||||
type: TRAVEL_GUIDE_ACTION,
|
||||
getIconType: () => 'popout',
|
||||
getDisplayName: () => 'View travel guide',
|
||||
execute: async ({ country }) => {
|
||||
window.open(`https://www.worldtravelguide.net/?s=${country},`, '_blank');
|
||||
},
|
||||
});
|
||||
|
||||
export const viewInMapsAction = createAction<{ country: string }>({
|
||||
type: VIEW_IN_MAPS_ACTION,
|
||||
getIconType: () => 'popout',
|
||||
getDisplayName: () => 'View in maps',
|
||||
execute: async ({ country }) => {
|
||||
window.open(`https://www.google.com/maps/place/${country}`, '_blank');
|
||||
},
|
||||
});
|
||||
|
||||
export interface User {
|
||||
phone?: string;
|
||||
countryOfResidence: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function EditUserModal({
|
||||
user,
|
||||
update,
|
||||
close,
|
||||
}: {
|
||||
user: User;
|
||||
update: (user: User) => void;
|
||||
close: () => void;
|
||||
}) {
|
||||
const [name, setName] = useState(user.name);
|
||||
return (
|
||||
<EuiModalBody>
|
||||
<EuiFieldText prepend="Name" value={name} onChange={e => setName(e.target.value)} />
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
update({ ...user, name });
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</EuiButton>
|
||||
</EuiModalBody>
|
||||
);
|
||||
}
|
||||
|
||||
export const createEditUserAction = (getOpenModal: () => Promise<OverlayStart['openModal']>) =>
|
||||
createAction<{
|
||||
user: User;
|
||||
update: (user: User) => void;
|
||||
}>({
|
||||
type: EDIT_USER_ACTION,
|
||||
getIconType: () => 'pencil',
|
||||
getDisplayName: () => 'Edit user',
|
||||
execute: async ({ user, update }) => {
|
||||
const overlay = (await getOpenModal())(
|
||||
toMountPoint(<EditUserModal user={user} update={update} close={() => overlay.close()} />)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const createPhoneUserAction = (getUiActionsApi: () => Promise<UiActionsStart>) =>
|
||||
createAction<{
|
||||
user: User;
|
||||
update: (user: User) => void;
|
||||
}>({
|
||||
type: PHONE_USER_ACTION,
|
||||
getDisplayName: () => 'Call phone number',
|
||||
isCompatible: async ({ user }) => user.phone !== undefined,
|
||||
execute: async ({ user }) => {
|
||||
// One option - execute the more specific action directly.
|
||||
// makePhoneCallAction.execute({ phone: user.phone });
|
||||
|
||||
// Another option - emit the trigger and automatically get *all* the actions attached
|
||||
// to the phone number trigger.
|
||||
// TODO: we need to figure out the best way to handle these nested actions however, since
|
||||
// we don't want multiple context menu's to pop up.
|
||||
(await getUiActionsApi()).executeTriggerActions(PHONE_TRIGGER, { phone: user.phone });
|
||||
},
|
||||
});
|
124
examples/ui_actions_explorer/public/app.tsx
Normal file
124
examples/ui_actions_explorer/public/app.tsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 { EuiPage } from '@elastic/eui';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { EuiPageBody } from '@elastic/eui';
|
||||
import { EuiPageContent } from '@elastic/eui';
|
||||
import { EuiPageContentBody } from '@elastic/eui';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { EuiFieldText } from '@elastic/eui';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { EuiPageHeader } from '@elastic/eui';
|
||||
import { EuiModalBody } from '@elastic/eui';
|
||||
import { toMountPoint } from '../../../src/plugins/kibana_react/public';
|
||||
import { UiActionsStart, createAction } from '../../../src/plugins/ui_actions/public';
|
||||
import { AppMountParameters, OverlayStart } from '../../../src/core/public';
|
||||
import { HELLO_WORLD_TRIGGER_ID, HELLO_WORLD_ACTION_TYPE } from '../../ui_action_examples/public';
|
||||
import { TriggerContextExample } from './trigger_context_example';
|
||||
|
||||
interface Props {
|
||||
uiActionsApi: UiActionsStart;
|
||||
openModal: OverlayStart['openModal'];
|
||||
}
|
||||
|
||||
const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => {
|
||||
const [name, setName] = useState('Waldo');
|
||||
const [confirmationText, setConfirmationText] = useState('');
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPageHeader>Ui Actions Explorer</EuiPageHeader>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>
|
||||
<EuiText>
|
||||
<p>
|
||||
By default there is a single action attached to the `HELLO_WORLD_TRIGGER`. Clicking
|
||||
this button will cause it to be executed immediately.
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiButton
|
||||
data-test-subj="emitHelloWorldTrigger"
|
||||
onClick={() => uiActionsApi.executeTriggerActions(HELLO_WORLD_TRIGGER_ID, {})}
|
||||
>
|
||||
Say hello world!
|
||||
</EuiButton>
|
||||
|
||||
<EuiText>
|
||||
<p>
|
||||
Lets dynamically add new actions to this trigger. After you click this button, click
|
||||
the above button again. This time it should offer you multiple options to choose
|
||||
from. Using the UI Action and Trigger API makes your plugin extensible by other
|
||||
plugins. Any actions attached to the `HELLO_WORLD_TRIGGER_ID` will show up here!
|
||||
</p>
|
||||
<EuiFieldText prepend="Name" value={name} onChange={e => setName(e.target.value)} />
|
||||
<EuiButton
|
||||
data-test-subj="addDynamicAction"
|
||||
onClick={() => {
|
||||
const dynamicAction = createAction<{}>({
|
||||
type: `${HELLO_WORLD_ACTION_TYPE}-${name}`,
|
||||
getDisplayName: () => `Say hello to ${name}`,
|
||||
execute: async () => {
|
||||
const overlay = openModal(
|
||||
toMountPoint(
|
||||
<EuiModalBody>
|
||||
<EuiText data-test-subj="dynamicHelloWorldActionText">
|
||||
{`Hello ${name}`}
|
||||
</EuiText>{' '}
|
||||
<EuiButton data-test-subj="closeModal" onClick={() => overlay.close()}>
|
||||
Close
|
||||
</EuiButton>
|
||||
</EuiModalBody>
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
uiActionsApi.registerAction(dynamicAction);
|
||||
uiActionsApi.attachAction(HELLO_WORLD_TRIGGER_ID, dynamicAction.type);
|
||||
setConfirmationText(
|
||||
`You've successfully added a new action: ${dynamicAction.getDisplayName(
|
||||
{}
|
||||
)}. Refresh the page to reset state. It's up to the user of the system to persist state like this.`
|
||||
);
|
||||
}}
|
||||
>
|
||||
Say hello to me!
|
||||
</EuiButton>
|
||||
{confirmationText !== '' ? <EuiCallOut>{confirmationText}</EuiCallOut> : undefined}
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
<TriggerContextExample uiActionsApi={uiActionsApi} />
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
};
|
||||
|
||||
export const renderApp = (props: Props, { element }: AppMountParameters) => {
|
||||
ReactDOM.render(<ActionsExplorer {...props} />, element);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
22
examples/ui_actions_explorer/public/index.ts
Normal file
22
examples/ui_actions_explorer/public/index.ts
Normal file
|
@ -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 { UiActionsExplorerPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new UiActionsExplorerPlugin();
|
51
examples/ui_actions_explorer/public/page.tsx
Normal file
51
examples/ui_actions_explorer/public/page.tsx
Normal file
|
@ -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 React from 'react';
|
||||
|
||||
import {
|
||||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiPageHeader,
|
||||
EuiPageHeaderSection,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
interface PageProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Page({ title, children }: PageProps) {
|
||||
return (
|
||||
<EuiPageBody data-test-subj="searchTestPage">
|
||||
<EuiPageHeader>
|
||||
<EuiPageHeaderSection>
|
||||
<EuiTitle size="l">
|
||||
<h1>{title}</h1>
|
||||
</EuiTitle>
|
||||
</EuiPageHeaderSection>
|
||||
</EuiPageHeader>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>{children}</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
);
|
||||
}
|
110
examples/ui_actions_explorer/public/plugin.tsx
Normal file
110
examples/ui_actions_explorer/public/plugin.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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, CoreSetup, AppMountParameters } from 'kibana/public';
|
||||
import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public';
|
||||
import { ISearchAppMountContext } from '../../../src/plugins/data/public';
|
||||
import {
|
||||
PHONE_TRIGGER,
|
||||
USER_TRIGGER,
|
||||
COUNTRY_TRIGGER,
|
||||
createPhoneUserAction,
|
||||
lookUpWeatherAction,
|
||||
viewInMapsAction,
|
||||
createEditUserAction,
|
||||
CALL_PHONE_NUMBER_ACTION,
|
||||
VIEW_IN_MAPS_ACTION,
|
||||
TRAVEL_GUIDE_ACTION,
|
||||
PHONE_USER_ACTION,
|
||||
EDIT_USER_ACTION,
|
||||
makePhoneCallAction,
|
||||
showcasePluggability,
|
||||
SHOWCASE_PLUGGABILITY_ACTION,
|
||||
} from './actions/actions';
|
||||
|
||||
declare module 'kibana/public' {
|
||||
interface AppMountContext {
|
||||
search?: ISearchAppMountContext;
|
||||
}
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
uiActions: UiActionsStart;
|
||||
}
|
||||
|
||||
interface SetupDeps {
|
||||
uiActions: UiActionsSetup;
|
||||
}
|
||||
|
||||
export class UiActionsExplorerPlugin implements Plugin<void, void, {}, StartDeps> {
|
||||
public setup(core: CoreSetup<{ uiActions: UiActionsStart }>, deps: SetupDeps) {
|
||||
deps.uiActions.registerTrigger({
|
||||
id: COUNTRY_TRIGGER,
|
||||
actionIds: [],
|
||||
});
|
||||
deps.uiActions.registerTrigger({
|
||||
id: PHONE_TRIGGER,
|
||||
actionIds: [],
|
||||
});
|
||||
deps.uiActions.registerTrigger({
|
||||
id: USER_TRIGGER,
|
||||
actionIds: [],
|
||||
});
|
||||
deps.uiActions.registerAction(lookUpWeatherAction);
|
||||
deps.uiActions.registerAction(viewInMapsAction);
|
||||
deps.uiActions.registerAction(makePhoneCallAction);
|
||||
deps.uiActions.registerAction(showcasePluggability);
|
||||
|
||||
const startServices = core.getStartServices();
|
||||
deps.uiActions.registerAction(
|
||||
createPhoneUserAction(async () => (await startServices)[1].uiActions)
|
||||
);
|
||||
deps.uiActions.registerAction(
|
||||
createEditUserAction(async () => (await startServices)[0].overlays.openModal)
|
||||
);
|
||||
deps.uiActions.attachAction(USER_TRIGGER, PHONE_USER_ACTION);
|
||||
deps.uiActions.attachAction(USER_TRIGGER, EDIT_USER_ACTION);
|
||||
|
||||
// What's missing here is type analysis to ensure the context emitted by the trigger
|
||||
// is the same context that the action requires.
|
||||
deps.uiActions.attachAction(COUNTRY_TRIGGER, VIEW_IN_MAPS_ACTION);
|
||||
deps.uiActions.attachAction(COUNTRY_TRIGGER, TRAVEL_GUIDE_ACTION);
|
||||
deps.uiActions.attachAction(COUNTRY_TRIGGER, SHOWCASE_PLUGGABILITY_ACTION);
|
||||
deps.uiActions.attachAction(PHONE_TRIGGER, CALL_PHONE_NUMBER_ACTION);
|
||||
deps.uiActions.attachAction(PHONE_TRIGGER, SHOWCASE_PLUGGABILITY_ACTION);
|
||||
deps.uiActions.attachAction(USER_TRIGGER, SHOWCASE_PLUGGABILITY_ACTION);
|
||||
|
||||
core.application.register({
|
||||
id: 'uiActionsExplorer',
|
||||
title: 'Ui Actions Explorer',
|
||||
async mount(params: AppMountParameters) {
|
||||
const [coreStart, depsStart] = await core.getStartServices();
|
||||
const { renderApp } = await import('./app');
|
||||
return renderApp(
|
||||
{ uiActionsApi: depsStart.uiActions, openModal: coreStart.overlays.openModal },
|
||||
params
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start() {}
|
||||
|
||||
public stop() {}
|
||||
}
|
151
examples/ui_actions_explorer/public/trigger_context_example.tsx
Normal file
151
examples/ui_actions_explorer/public/trigger_context_example.tsx
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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, { Fragment, useMemo, useState } from 'react';
|
||||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { EuiDataGrid } from '@elastic/eui';
|
||||
import { EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
|
||||
import { USER_TRIGGER, PHONE_TRIGGER, COUNTRY_TRIGGER, User } from './actions/actions';
|
||||
|
||||
export interface Props {
|
||||
uiActionsApi: UiActionsStart;
|
||||
}
|
||||
|
||||
interface UserRowData {
|
||||
name: string;
|
||||
countryOfResidence: React.ReactNode;
|
||||
phone: React.ReactNode;
|
||||
rowActions: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const createRowData = (
|
||||
user: User,
|
||||
uiActionsApi: UiActionsStart,
|
||||
update: (newUser: User, oldName: string) => void
|
||||
) => ({
|
||||
name: user.name,
|
||||
countryOfResidence: (
|
||||
<Fragment>
|
||||
<EuiButtonEmpty
|
||||
onClick={() => {
|
||||
uiActionsApi.executeTriggerActions(COUNTRY_TRIGGER, {
|
||||
country: user.countryOfResidence,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.countryOfResidence}
|
||||
</EuiButtonEmpty>
|
||||
</Fragment>
|
||||
),
|
||||
phone: (
|
||||
<Fragment>
|
||||
<EuiButtonEmpty
|
||||
onClick={() => {
|
||||
uiActionsApi.executeTriggerActions(PHONE_TRIGGER, {
|
||||
phone: user.phone,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.phone}
|
||||
</EuiButtonEmpty>
|
||||
</Fragment>
|
||||
),
|
||||
rowActions: (
|
||||
<Fragment>
|
||||
<EuiButtonEmpty
|
||||
onClick={() => {
|
||||
uiActionsApi.executeTriggerActions(USER_TRIGGER, {
|
||||
user,
|
||||
update: (newUser: User) => update(newUser, user.name),
|
||||
});
|
||||
}}
|
||||
>
|
||||
Actions
|
||||
</EuiButtonEmpty>
|
||||
</Fragment>
|
||||
),
|
||||
});
|
||||
|
||||
export function TriggerContextExample({ uiActionsApi }: Props) {
|
||||
const columns = [
|
||||
{
|
||||
id: 'name',
|
||||
},
|
||||
{
|
||||
id: 'countryOfResidence',
|
||||
},
|
||||
{
|
||||
id: 'phone',
|
||||
},
|
||||
{
|
||||
id: 'rowActions',
|
||||
},
|
||||
];
|
||||
|
||||
const rawData = [
|
||||
{ name: 'Sue', countryOfResidence: 'USA', phone: '1-519-555-1234' },
|
||||
{ name: 'Bob', countryOfResidence: 'Germany' },
|
||||
{ name: 'Tom', countryOfResidence: 'Russia', phone: '45-555-444-1234' },
|
||||
];
|
||||
|
||||
const updateUser = (newUser: User, oldName: string) => {
|
||||
const index = rows.findIndex(u => u.name === oldName);
|
||||
const newRows = [...rows];
|
||||
newRows.splice(index, 1, createRowData(newUser, uiActionsApi, updateUser));
|
||||
setRows(newRows);
|
||||
};
|
||||
|
||||
const initialRows: UserRowData[] = rawData.map((user: User) =>
|
||||
createRowData(user, uiActionsApi, updateUser)
|
||||
);
|
||||
|
||||
const [rows, setRows] = useState(initialRows);
|
||||
|
||||
const renderCellValue = useMemo(() => {
|
||||
return ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
|
||||
return rows.hasOwnProperty(rowIndex) ? rows[rowIndex][columnId] : null;
|
||||
};
|
||||
}, [rows]);
|
||||
|
||||
return (
|
||||
<EuiText>
|
||||
<h1>Triggers that emit context</h1>
|
||||
<p>
|
||||
The trigger above did not emit any context, but a trigger can, and if it does, it will be
|
||||
passed to the action when it is executed. This is helpful for dynamic data that is only
|
||||
known at the time the trigger is emitted. Lets explore a use case where the is dynamic. The
|
||||
following data grid emits a few triggers, each with a some actions attached.
|
||||
</p>
|
||||
|
||||
<EuiDataGrid
|
||||
aria-label="Action and trigger data demo"
|
||||
columns={columns}
|
||||
renderCellValue={renderCellValue}
|
||||
rowCount={rawData.length}
|
||||
columnVisibility={{
|
||||
visibleColumns: ['name', 'phone', 'countryOfResidence', 'rowActions'],
|
||||
setVisibleColumns: () => {},
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
14
examples/ui_actions_explorer/tsconfig.json
Normal file
14
examples/ui_actions_explorer/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"../../typings/**/*",
|
||||
],
|
||||
"exclude": []
|
||||
}
|
|
@ -24,7 +24,11 @@ export default async function({ readConfigFile }) {
|
|||
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./search'), require.resolve('./embeddables')],
|
||||
testFiles: [
|
||||
require.resolve('./search'),
|
||||
require.resolve('./embeddables'),
|
||||
require.resolve('./ui_actions'),
|
||||
],
|
||||
services: {
|
||||
...functionalConfig.get('services'),
|
||||
...services,
|
||||
|
|
41
test/examples/ui_actions/index.ts
Normal file
41
test/examples/ui_actions/index.ts
Normal file
|
@ -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 { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({
|
||||
getService,
|
||||
getPageObjects,
|
||||
loadTestFile,
|
||||
}: PluginFunctionalProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const appsMenu = getService('appsMenu');
|
||||
const PageObjects = getPageObjects(['common', 'header']);
|
||||
|
||||
describe('ui actions explorer', function() {
|
||||
before(async () => {
|
||||
await browser.setWindowSize(1300, 900);
|
||||
await PageObjects.common.navigateToApp('settings');
|
||||
await appsMenu.clickLink('Ui Actions Explorer');
|
||||
});
|
||||
|
||||
loadTestFile(require.resolve('./ui_actions'));
|
||||
});
|
||||
}
|
53
test/examples/ui_actions/ui_actions.ts
Normal file
53
test/examples/ui_actions/ui_actions.ts
Normal file
|
@ -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 expect from '@kbn/expect';
|
||||
|
||||
import { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService }: PluginFunctionalProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
|
||||
describe('', () => {
|
||||
it('hello world action', async () => {
|
||||
await testSubjects.click('emitHelloWorldTrigger');
|
||||
await retry.try(async () => {
|
||||
const text = await testSubjects.getVisibleText('helloWorldActionText');
|
||||
expect(text).to.be('Hello world!');
|
||||
});
|
||||
|
||||
await testSubjects.click('closeModal');
|
||||
});
|
||||
|
||||
it('dynamic hello world action', async () => {
|
||||
await testSubjects.click('addDynamicAction');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.click('emitHelloWorldTrigger');
|
||||
await testSubjects.click('embeddablePanelAction-HELLO_WORLD_ACTION_TYPE-Waldo');
|
||||
});
|
||||
await retry.try(async () => {
|
||||
const text = await testSubjects.getVisibleText('dynamicHelloWorldActionText');
|
||||
expect(text).to.be('Hello Waldo');
|
||||
});
|
||||
await testSubjects.click('closeModal');
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue