Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Doc] UI actions explorer #3614

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
};