Skip to content

Commit

Permalink
[Doc] UI actions explorer (#3614)
Browse files Browse the repository at this point in the history
* adds ui actions explorer

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>

* updates UI actions readme

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>

* updates UI actions readme again

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>

* cleans up developer examples view

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>

* adds changelog

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>

---------

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>
  • Loading branch information
ashwin-pc authored Mar 17, 2023
1 parent 725a2a1 commit c9211b9
Show file tree
Hide file tree
Showing 12 changed files with 448 additions and 147 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Security] Bumps hapi/statehood to 7.0.4 ([#3411](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3411))
- [CVE-2023-25166] Bump formula to 3.0.1 ([#3416](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3416))
- [CVE-2023-25653] Bump node-jose to 2.2.0 ([#3445](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3445))
- [CVE-2023-26486][CVE-2023-26487] Bump vega from 5.22.1 to 5.23.0 ([#3533](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3533))
- [CVE-2023-26486][cve-2023-26487] Bump vega from 5.22.1 to 5.23.0 ([#3533](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3533))

### 📈 Features/Enhancements

Expand Down Expand Up @@ -132,6 +132,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Correct copyright date range of NOTICE file and notice generator ([#3308](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3308))
- Simplify the in-code instructions for upgrading `re2` ([#3328](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3328))
- [Doc] Add docker dev set up instruction ([#3444](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3444))
- [Doc] UI actions explorer ([#3614](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3614))

### 🛠 Maintenance

Expand Down
44 changes: 26 additions & 18 deletions examples/developer_examples/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@ import {
EuiPageContent,
EuiCard,
EuiPageContentHeader,
EuiFlexGroup,
EuiFlexItem,
EuiFieldSearch,
EuiListGroup,
EuiHighlight,
EuiLink,
EuiButtonIcon,
EuiPage,
} from '@elastic/eui';
import { AppMountParameters } from '../../../src/core/public';
import { ExampleDefinition } from './types';
Expand All @@ -66,26 +65,33 @@ function DeveloperExamples({ examples, navigateToApp, getUrlForApp }: Props) {
});

return (
<EuiPageContent>
<EuiPageContentHeader>
<EuiText>
<h1>Developer examples</h1>
<p>
The following examples showcase services and APIs that are available to developers.
<EuiPage restrictWidth="1500px">
<EuiPageContent>
<EuiPageContentHeader>
<EuiText>
<h1>Developer examples</h1>
<p>
The following examples showcase services and APIs that are available to developers.
</p>
<EuiFieldSearch
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
isClearable={true}
aria-label="Search developer examples"
/>
</p>
</EuiText>
</EuiPageContentHeader>
<EuiFlexGroup wrap>
{filteredExamples.map((def) => (
<EuiFlexItem style={{ minWidth: 300, maxWidth: 500 }} key={def.appId}>
</EuiText>
</EuiPageContentHeader>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
gap: 16, // $euiSize
}}
>
{filteredExamples.map((def) => (
<EuiCard
key={def.appId}
description={
<EuiHighlight search={search} highlightAll={true}>
{def.description}
Expand Down Expand Up @@ -114,11 +120,13 @@ function DeveloperExamples({ examples, navigateToApp, getUrlForApp }: Props) {
}
image={def.image}
footer={def.links ? <EuiListGroup size={'s'} listItems={def.links} /> : undefined}
titleSize="xs"
textAlign="left"
/>
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiPageContent>
))}
</div>
</EuiPageContent>
</EuiPage>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export function ExplorerTab() {
const allTypes = new Set(Object.values(functions).map((fn) => fn.type));

// Catch all filter and remove
allTypes.delete(undefined);
allTypes.add('all');

return [...allTypes].map((type) => ({ text: type }));
Expand Down
6 changes: 3 additions & 3 deletions examples/state_containers_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class StateContainersExamplesPlugin implements Plugin {

developerExamples.register({
appId: 'stateContainersExampleBrowserHistory',
title: 'State containers using browser history',
title: 'State containers: browser history',
description: `An example todo app that uses browser history and state container utilities like createStateContainerReactHelpers,
createStateContainer, createOsdUrlStateStorage, createSessionStorageStateStorage,
syncStates and getStateFromOsdUrl to keep state in sync with the URL. Change some parameters, navigate away and then back, and the
Expand All @@ -101,7 +101,7 @@ export class StateContainersExamplesPlugin implements Plugin {

developerExamples.register({
appId: 'stateContainersExampleHashHistory',
title: 'State containers using hash history',
title: 'State containers: hash history',
description: `An example todo app that uses hash history and state container utilities like createStateContainerReactHelpers,
createStateContainer, createOsdUrlStateStorage, createSessionStorageStateStorage,
syncStates and getStateFromOsdUrl to keep state in sync with the URL. Change some parameters, navigate away and then back, and the
Expand All @@ -120,7 +120,7 @@ export class StateContainersExamplesPlugin implements Plugin {

developerExamples.register({
appId: PLUGIN_ID,
title: 'Sync state from a query bar with the url',
title: 'State containers: Sync with the url',
description: `Shows how to use data.syncQueryStateWitUrl in combination with state container utilities from opensearch_dashboards_utils to
show a query bar that stores state in the url and is kept in sync.
`,
Expand Down
180 changes: 81 additions & 99 deletions examples/ui_actions_explorer/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,114 +28,96 @@
* under the License.
*/

import React, { useState } from 'react';
import React, { useMemo } from 'react';
import ReactDOM from 'react-dom';
import { FormattedMessage, I18nProvider } from '@osd/i18n/react';

import { EuiPage } from '@elastic/eui';
import {
EuiPage,
EuiTitle,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiTabbedContent,
} from '@elastic/eui';
import { AppMountParameters, CoreStart } from '../../../src/core/public';
import { UiActionsExplorerServices, UiActionsExplorerStartDependencies } from './types';
import { OpenSearchDashboardsContextProvider } from '../../../src/plugins/opensearch_dashboards_react/public';

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/opensearch_dashboards_react/public';
import { UiActionsStart, createAction } from '../../../src/plugins/ui_actions/public';
import { AppMountParameters, OverlayStart } from '../../../src/core/public';
import { HELLO_WORLD_TRIGGER_ID, ACTION_HELLO_WORLD } from '../../ui_action_examples/public';
import { TriggerContextExample } from './trigger_context_example';
import { ContextMenuExamples } from './context_menu_examples';
import { BasicTab } from './basic_tab';
import { ExplorerTab } from './explorer_tab';

interface Props {
uiActionsApi: UiActionsStart;
openModal: OverlayStart['openModal'];
}
const ActionsExplorer = () => {
const tabs = useMemo(
() => [
{
id: 'demo-basic',
name: (
<FormattedMessage
id="uiActionsExplorer.demoBasic.TabTitle"
defaultMessage="{name}"
values={{ name: 'Basic' }}
/>
),
content: <BasicTab />,
},
{
id: 'demo-explorer',
name: (
<FormattedMessage
id="uiActionsExplorer.demoExplorer.TabTitle"
defaultMessage="{name}"
values={{ name: 'Explorer' }}
/>
),
content: <ExplorerTab />,
},
],
[]
);

const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => {
const [name, setName] = useState('Waldo');
const [confirmationText, setConfirmationText] = useState('');
return (
<EuiPage>
<EuiPageBody component="main">
<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<typeof ACTION_HELLO_WORLD>({
id: `${ACTION_HELLO_WORLD}-${name}`,
type: ACTION_HELLO_WORLD,
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.addTriggerAction(HELLO_WORLD_TRIGGER_ID, dynamicAction);
setConfirmationText(
`You've successfully added a new action: ${dynamicAction.getDisplayName({
trigger: uiActionsApi.getTrigger(HELLO_WORLD_TRIGGER_ID),
})}. 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} />

<EuiSpacer />

<ContextMenuExamples />
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<I18nProvider>
<EuiPage restrictWidth="1500px">
<EuiPageBody component="main">
<EuiPageHeader>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="uiAsctionsExample.appTitle"
defaultMessage="{name}"
values={{ name: 'UI Actions' }}
/>
</h1>
</EuiTitle>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>
);
};

export const renderApp = (props: Props, { element }: AppMountParameters) => {
ReactDOM.render(<ActionsExplorer {...props} />, element);
export const renderApp = (
coreStart: CoreStart,
{ uiActions }: UiActionsExplorerStartDependencies,
{ element }: AppMountParameters
) => {
const services: UiActionsExplorerServices = {
...coreStart,
uiActions,
};
ReactDOM.render(
<OpenSearchDashboardsContextProvider services={services}>
<ActionsExplorer />
</OpenSearchDashboardsContextProvider>,
element
);

return () => ReactDOM.unmountComponentAtNode(element);
};
Loading

0 comments on commit c9211b9

Please sign in to comment.