mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Drilldowns in examples (#75640)
* feat: 🎸 add telemetry for in-chart "Explore underlying data" * feat: 🎸 add telemetry for in-chart "Explore underlying data" * refactor: 💡 move all drilldowns into a sub-folder * feat: 🎸 setup example app section for ui_actions_enhanced * feat: 🎸 set up Drilldown Manager section * feat: 🎸 open drilldown manager from example plugin * refactor: 💡 rename supportedTriggers -> triggers prop * feat: 🎸 show dev warning if triggers prop is empty * refactor: 💡 rename "supportedTriggers" -> "triggers" props * feat: 🎸 open and close drilldown manager from example plugin * feat: 🎸 add sample ML job trigger * feat: 🎸 add sample ML URL drilldown * refactor: 💡 move KibanaURL to share plugin * refactor: 💡 add index file to ml drilldown * feat: 🎸 add AbstractDashboardDrilldown * refactor: 💡 make dashboard drilldown use abstract drilldown * refactor: 💡 rename dashboard drilldown to embeddable drilldown * feat: 🎸 add Dashboard drilldown to sample plugin * feat: 🎸 open dashboard drilldown in list view * feat: 🎸 add drilldown execute button * refactor: 💡 move drilldown React hooks into /hooks folder * test: 💍 fix tests after renaming triggers * chore: 🤖 populate "requireBundles" * fix: 🐛 fix TypeScript errors * fix: 🐛 fix Kibana plugin dependency * chore: 🤖 remoe unused import * feat: 🎸 persist drilldown manager state across app navigations * refactor: 💡 move no-embeddable example into a seprate file * feat: 🎸 set up example with embeddable * feat: 🎸 improve embeddable example * refactor: 💡 rename without embeddable example * feat: 🎸 set up no-embeddable single click example * feat: 🎸 add dashboard drilldown to single button example * fix: 🐛 remove unused margin * fix: 🐛 make "Get more actions" translation static * chore: 🤖 remove old dashboard drilldown definition * refactor: 💡 rename samples to generic names * refactor: 💡 make app 1 example drilldown "hello world" * chore: 🤖 remove unused required bundle * chore: 🤖 add dashboardEnhanced back * [kbn/optimizer] only build xpack examples when building xpack plugins * move alerting_example into x-pack/examples * remove filter for alertingExample plugin in oss plugins CI step * revert unrelated change * fix: 🐛 use correct prop name * test: 💍 fix embeddable-to-dashboard drilldown mock * test: 💍 fix a test after refactor * chore: 🤖 remove unused import * chore: 🤖 add dashboard_enahcned to example plugin * chore: 🤖 address review comments * feat: 🎸 add description to UI Actions Enhanced examples * docs: ✏️ improve docs of example plugin Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
13a737e675
commit
59e4e06316
92 changed files with 1687 additions and 493 deletions
|
@ -31,7 +31,12 @@ export {
|
|||
} from './application';
|
||||
export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
|
||||
|
||||
export { DashboardStart, DashboardUrlGenerator, DashboardFeatureFlagConfig } from './plugin';
|
||||
export {
|
||||
DashboardSetup,
|
||||
DashboardStart,
|
||||
DashboardUrlGenerator,
|
||||
DashboardFeatureFlagConfig,
|
||||
} from './plugin';
|
||||
export {
|
||||
DASHBOARD_APP_URL_GENERATOR,
|
||||
createDashboardUrlGenerator,
|
||||
|
|
|
@ -145,7 +145,7 @@ interface StartDependencies {
|
|||
savedObjects: SavedObjectsStart;
|
||||
}
|
||||
|
||||
export type Setup = void;
|
||||
export type DashboardSetup = void;
|
||||
|
||||
export interface DashboardStart {
|
||||
getSavedDashboardLoader: () => SavedObjectLoader;
|
||||
|
@ -180,7 +180,7 @@ declare module '../../../plugins/ui_actions/public' {
|
|||
}
|
||||
|
||||
export class DashboardPlugin
|
||||
implements Plugin<Setup, DashboardStart, SetupDependencies, StartDependencies> {
|
||||
implements Plugin<DashboardSetup, DashboardStart, SetupDependencies, StartDependencies> {
|
||||
constructor(private initializerContext: PluginInitializerContext) {}
|
||||
|
||||
private appStateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
|
||||
|
@ -193,17 +193,8 @@ export class DashboardPlugin
|
|||
|
||||
public setup(
|
||||
core: CoreSetup<StartDependencies, DashboardStart>,
|
||||
{
|
||||
share,
|
||||
uiActions,
|
||||
embeddable,
|
||||
home,
|
||||
kibanaLegacy,
|
||||
urlForwarding,
|
||||
data,
|
||||
usageCollection,
|
||||
}: SetupDependencies
|
||||
): Setup {
|
||||
{ share, uiActions, embeddable, home, urlForwarding, data, usageCollection }: SetupDependencies
|
||||
): DashboardSetup {
|
||||
this.dashboardFeatureFlagConfig = this.initializerContext.config.get<
|
||||
DashboardFeatureFlagConfig
|
||||
>();
|
||||
|
|
|
@ -40,4 +40,6 @@ export {
|
|||
|
||||
import { SharePlugin } from './plugin';
|
||||
|
||||
export { KibanaURL } from './kibana_url';
|
||||
|
||||
export const plugin = () => new SharePlugin();
|
||||
|
|
44
src/plugins/share/public/kibana_url.ts
Normal file
44
src/plugins/share/public/kibana_url.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// TODO: Replace this logic with KibanaURL once it is available.
|
||||
// https://github.com/elastic/kibana/issues/64497
|
||||
export class KibanaURL {
|
||||
public readonly path: string;
|
||||
public readonly appName: string;
|
||||
public readonly appPath: string;
|
||||
|
||||
constructor(path: string) {
|
||||
const match = path.match(/^.*\/app\/([^\/#]+)(.+)$/);
|
||||
|
||||
if (!match) {
|
||||
throw new Error('Unexpected URL path.');
|
||||
}
|
||||
|
||||
const [, appName, appPath] = match;
|
||||
|
||||
if (!appName || !appPath) {
|
||||
throw new Error('Could not parse URL path.');
|
||||
}
|
||||
|
||||
this.path = path;
|
||||
this.appName = appName;
|
||||
this.appPath = appPath;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ To run this example plugin, use the command `yarn start --run-examples`.
|
|||
|
||||
## Drilldown examples
|
||||
|
||||
This plugin holds few examples on how to add drilldown types to dashboard.
|
||||
This plugin holds few examples on how to add drilldown types to dashboard. See
|
||||
`./public/drilldowns/` folder.
|
||||
|
||||
To play with drilldowns, open any dashboard, click "Edit" to put it in *edit mode*.
|
||||
Now when opening context menu of dashboard panels you should see "Create drilldown" option.
|
||||
|
@ -34,3 +35,74 @@ One can see how middle-click or Ctrl + click behavior could be supported using
|
|||
### `dashboard_to_discover_drilldown`
|
||||
|
||||
`dashboard_to_discover_drilldown` shows how a real-world drilldown could look like.
|
||||
|
||||
|
||||
## Drilldown Manager examples
|
||||
|
||||
*Drilldown Manager* is a collectio of code and React components that allows you
|
||||
to add drilldowns to any app. To see examples of how drilldows can be added to
|
||||
your app, run Kibana with `--run-examples` flag:
|
||||
|
||||
```
|
||||
yarn start --run-examples
|
||||
```
|
||||
|
||||
Then go to "Developer examples" and "UI Actions Enhanced", where you can see examples
|
||||
where *Drilldown Manager* is used outside of the Dashboard app:
|
||||
|
||||

|
||||
|
||||
These examples show how you can create your custom UI Actions triggers and add
|
||||
drilldowns to them, or use an embeddable in your app and add drilldows to it.
|
||||
|
||||
|
||||
### Trigger examples
|
||||
|
||||
The `/public/triggers` folder shows how you can create custom triggers for your app.
|
||||
Triggers are things that trigger some action in UI, like "user click".
|
||||
|
||||
Once you have defined your triggers, you need to register them in your plugin:
|
||||
|
||||
```ts
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core, { uiActionsEnhanced: uiActions }: SetupDependencies) {
|
||||
uiActions.registerTrigger(myTrigger);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `app1_hello_world_drilldown`
|
||||
|
||||
`app1_hello_world_drilldown` is a basic example that shows how you can add the most
|
||||
basic drilldown to your custom trigger.
|
||||
|
||||
### `appx_to_dashboard_drilldown`
|
||||
|
||||
`app1_to_dashboard_drilldown` and `app2_to_dashboard_drilldown` show how the Dashboard
|
||||
drilldown can be used in other apps, outside of Dashboard.
|
||||
|
||||
Basically you define it:
|
||||
|
||||
```ts
|
||||
type Trigger = typeof MY_TRIGGER_TRIGGER;
|
||||
type Context = MyAppClickContext;
|
||||
|
||||
export class App1ToDashboardDrilldown extends AbstractDashboardDrilldown<Trigger> {
|
||||
public readonly supportedTriggers = () => [MY_TRIGGER] as Trigger[];
|
||||
|
||||
protected async getURL(config: Config, context: Context): Promise<KibanaURL> {
|
||||
return 'https://...';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and then you register it in your plugin:
|
||||
|
||||
```ts
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core, { uiActionsEnhanced: uiActions }: SetupDependencies) {
|
||||
const drilldown = new App2ToDashboardDrilldown(/* You can pass in dependencies here. */);
|
||||
uiActions.registerDrilldown(drilldown);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -5,10 +5,21 @@
|
|||
"configPath": ["ui_actions_enhanced_examples"],
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["uiActions","uiActionsEnhanced", "data", "discover"],
|
||||
"requiredPlugins": [
|
||||
"uiActions",
|
||||
"uiActionsEnhanced",
|
||||
"data",
|
||||
"discover",
|
||||
"dashboard",
|
||||
"dashboardEnhanced",
|
||||
"developerExamples"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": [
|
||||
"dashboardEnhanced",
|
||||
"embeddable",
|
||||
"kibanaUtils",
|
||||
"kibanaReact"
|
||||
"kibanaReact",
|
||||
"share"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiPageHeader,
|
||||
EuiPageHeaderSection,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export interface PageProps {
|
||||
title?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Page: React.FC<PageProps> = ({ title = 'Untitled', children }) => {
|
||||
return (
|
||||
<EuiPageBody style={{ maxWidth: 1200, margin: '0 auto' }}>
|
||||
<EuiPageHeader>
|
||||
<EuiPageHeaderSection>
|
||||
<EuiTitle size="l">
|
||||
<h1>{title}</h1>
|
||||
</EuiTitle>
|
||||
</EuiPageHeaderSection>
|
||||
</EuiPageHeader>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody style={{ maxWidth: 800, margin: '0 auto' }}>
|
||||
{children}
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './section';
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
export interface Props {
|
||||
title: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Section: React.FC<Props> = ({ title, children }) => {
|
||||
return (
|
||||
<section>
|
||||
<EuiTitle size="m">
|
||||
<h2>{title}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer />
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiPage } from '@elastic/eui';
|
||||
import { Page } from '../../components/page';
|
||||
import { DrilldownsManager } from '../drilldowns_manager';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
return (
|
||||
<EuiPage>
|
||||
<Page title={'UI Actions Enhanced'}>
|
||||
<DrilldownsManager />
|
||||
</Page>
|
||||
</EuiPage>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './app';
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiHorizontalRule } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { Section } from '../../components/section/section';
|
||||
import { SampleMlJob, SampleApp1ClickContext } from '../../triggers';
|
||||
import { DrilldownsWithoutEmbeddableExample } from '../drilldowns_without_embeddable_example';
|
||||
import { DrilldownsWithoutEmbeddableSingleButtonExample } from '../drilldowns_without_embeddable_single_button_example/drilldowns_without_embeddable_single_button_example';
|
||||
import { DrilldownsWithEmbeddableExample } from '../drilldowns_with_embeddable_example';
|
||||
|
||||
export const job: SampleMlJob = {
|
||||
job_id: '123',
|
||||
job_type: 'anomaly_detector',
|
||||
description: 'This is some ML job.',
|
||||
};
|
||||
|
||||
export const context: SampleApp1ClickContext = { job };
|
||||
|
||||
export const DrilldownsManager: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<Section title={'Drilldowns Manager'}>
|
||||
<DrilldownsWithoutEmbeddableExample />
|
||||
|
||||
<EuiHorizontalRule margin="xxl" />
|
||||
|
||||
<DrilldownsWithoutEmbeddableSingleButtonExample />
|
||||
|
||||
<EuiHorizontalRule margin="xxl" />
|
||||
|
||||
<DrilldownsWithEmbeddableExample />
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './drilldowns_manager';
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiButton,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
EuiFlyout,
|
||||
EuiCode,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
} from '@elastic/eui';
|
||||
import { SampleMlJob, SampleApp1ClickContext } from '../../triggers';
|
||||
import { EmbeddableRoot } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { ButtonEmbeddable } from '../../embeddables/button_embeddable';
|
||||
import { useUiActions } from '../../context';
|
||||
import { VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export const job: SampleMlJob = {
|
||||
job_id: '123',
|
||||
job_type: 'anomaly_detector',
|
||||
description: 'This is some ML job.',
|
||||
};
|
||||
|
||||
export const context: SampleApp1ClickContext = { job };
|
||||
|
||||
export const DrilldownsWithEmbeddableExample: React.FC = () => {
|
||||
const { plugins, managerWithEmbeddable } = useUiActions();
|
||||
const embeddable = React.useMemo(
|
||||
() =>
|
||||
new ButtonEmbeddable(
|
||||
{ id: 'DrilldownsWithEmbeddableExample' },
|
||||
{ uiActions: plugins.uiActionsEnhanced }
|
||||
),
|
||||
[plugins.uiActionsEnhanced]
|
||||
);
|
||||
const [showManager, setShowManager] = React.useState(false);
|
||||
const [openPopup, setOpenPopup] = React.useState(false);
|
||||
const viewRef = React.useRef<'create' | 'manage'>('create');
|
||||
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: 'Create new view',
|
||||
icon: 'plusInCircle',
|
||||
onClick: () => {
|
||||
setOpenPopup(false);
|
||||
viewRef.current = 'create';
|
||||
setShowManager((x) => !x);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Drilldown list view',
|
||||
icon: 'list',
|
||||
onClick: () => {
|
||||
setOpenPopup(false);
|
||||
viewRef.current = 'manage';
|
||||
setShowManager((x) => !x);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const openManagerButton = showManager ? (
|
||||
<EuiButton onClick={() => setShowManager(false)}>Close</EuiButton>
|
||||
) : (
|
||||
<EuiPopover
|
||||
id="contextMenuExample"
|
||||
button={
|
||||
<EuiButton
|
||||
fill={!showManager}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => setOpenPopup((x) => !x)}
|
||||
>
|
||||
Open Drilldown Manager
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={openPopup}
|
||||
closePopover={() => setOpenPopup(false)}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<h3>With embeddable example</h3>
|
||||
<p>
|
||||
This example shows how drilldown manager can be added to an embeddable which executes{' '}
|
||||
<EuiCode>VALUE_CLICK_TRIGGER</EuiCode> trigger. Below card is an embeddable which executes
|
||||
<EuiCode>VALUE_CLICK_TRIGGER</EuiCode> when it is clicked on.
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{openManagerButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div style={{ maxWidth: 200 }}>
|
||||
<EmbeddableRoot embeddable={embeddable} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{showManager && (
|
||||
<EuiFlyout onClose={() => setShowManager(false)} aria-labelledby="Drilldown Manager">
|
||||
<plugins.uiActionsEnhanced.FlyoutManageDrilldowns
|
||||
onClose={() => setShowManager(false)}
|
||||
viewMode={viewRef.current}
|
||||
dynamicActionManager={managerWithEmbeddable}
|
||||
triggers={[VALUE_CLICK_TRIGGER]}
|
||||
placeContext={{ embeddable }}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './drilldowns_with_embeddable_example';
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiFlyout,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
} from '@elastic/eui';
|
||||
import { useUiActions } from '../../context';
|
||||
import { SAMPLE_APP1_CLICK_TRIGGER, SampleMlJob, SampleApp1ClickContext } from '../../triggers';
|
||||
|
||||
export const job: SampleMlJob = {
|
||||
job_id: '123',
|
||||
job_type: 'anomaly_detector',
|
||||
description: 'This is some ML job.',
|
||||
};
|
||||
|
||||
export const context: SampleApp1ClickContext = { job };
|
||||
|
||||
export const DrilldownsWithoutEmbeddableExample: React.FC = () => {
|
||||
const { plugins, managerWithoutEmbeddable } = useUiActions();
|
||||
const [showManager, setShowManager] = React.useState(false);
|
||||
const [openPopup, setOpenPopup] = React.useState(false);
|
||||
const viewRef = React.useRef<'create' | 'manage'>('create');
|
||||
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: 'Create new view',
|
||||
icon: 'plusInCircle',
|
||||
onClick: () => {
|
||||
setOpenPopup(false);
|
||||
viewRef.current = 'create';
|
||||
setShowManager((x) => !x);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Drilldown list view',
|
||||
icon: 'list',
|
||||
onClick: () => {
|
||||
setOpenPopup(false);
|
||||
viewRef.current = 'manage';
|
||||
setShowManager((x) => !x);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const openManagerButton = showManager ? (
|
||||
<EuiButton onClick={() => setShowManager(false)}>Close</EuiButton>
|
||||
) : (
|
||||
<EuiPopover
|
||||
id="contextMenuExample"
|
||||
button={
|
||||
<EuiButton
|
||||
fill={!showManager}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => setOpenPopup((x) => !x)}
|
||||
>
|
||||
Open Drilldown Manager
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={openPopup}
|
||||
closePopover={() => setOpenPopup(false)}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<h3>Without embeddable example (app 1)</h3>
|
||||
<p>
|
||||
<em>Drilldown Manager</em> can be integrated into any app in Kibana. This example shows
|
||||
that drilldown manager can be used in an app which does not use embeddables and executes
|
||||
its custom UI Actions triggers.
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{openManagerButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
fill
|
||||
iconType="play"
|
||||
iconSide="left"
|
||||
onClick={() =>
|
||||
plugins.uiActionsEnhanced.executeTriggerActions(SAMPLE_APP1_CLICK_TRIGGER, context)
|
||||
}
|
||||
>
|
||||
Execute click action
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{showManager && (
|
||||
<EuiFlyout onClose={() => setShowManager(false)} aria-labelledby="Drilldown Manager">
|
||||
<plugins.uiActionsEnhanced.FlyoutManageDrilldowns
|
||||
onClose={() => setShowManager(false)}
|
||||
viewMode={viewRef.current}
|
||||
dynamicActionManager={managerWithoutEmbeddable}
|
||||
triggers={[SAMPLE_APP1_CLICK_TRIGGER]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './drilldowns_without_embeddable_example';
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer, EuiFlyout } from '@elastic/eui';
|
||||
import { useUiActions } from '../../context';
|
||||
import { sampleApp2ClickContext, SAMPLE_APP2_CLICK_TRIGGER } from '../../triggers';
|
||||
|
||||
export const DrilldownsWithoutEmbeddableSingleButtonExample: React.FC = () => {
|
||||
const { plugins, managerWithoutEmbeddableSingleButton } = useUiActions();
|
||||
const [showManager, setShowManager] = React.useState(false);
|
||||
const viewRef = React.useRef<'create' | 'manage'>('create');
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<h3>Without embeddable example, single button (app 2)</h3>
|
||||
<p>
|
||||
This example is the same as <em>Without embeddable example</em> but it shows that
|
||||
drilldown manager actions and user created drilldowns can be combined in one menu, this is
|
||||
useful, for example, for Canvas where clicking on a Canvas element would show the combined
|
||||
menu of drilldown manager actions and drilldown actions.
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
fill
|
||||
iconType="play"
|
||||
iconSide="left"
|
||||
onClick={() =>
|
||||
plugins.uiActionsEnhanced.executeTriggerActions(
|
||||
SAMPLE_APP2_CLICK_TRIGGER,
|
||||
sampleApp2ClickContext
|
||||
)
|
||||
}
|
||||
>
|
||||
Click this element
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{showManager && (
|
||||
<EuiFlyout onClose={() => setShowManager(false)} aria-labelledby="Drilldown Manager">
|
||||
<plugins.uiActionsEnhanced.FlyoutManageDrilldowns
|
||||
onClose={() => setShowManager(false)}
|
||||
viewMode={viewRef.current}
|
||||
dynamicActionManager={managerWithoutEmbeddableSingleButton}
|
||||
triggers={[SAMPLE_APP2_CLICK_TRIGGER]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './drilldowns_without_embeddable_single_button_example';
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { UiActionsEnhancedDynamicActionManager } from '../../../../plugins/ui_actions_enhanced/public';
|
||||
import { StartDependencies } from '../plugin';
|
||||
|
||||
export interface UiActionsExampleAppContextValue {
|
||||
appBasePath: string;
|
||||
core: CoreStart;
|
||||
plugins: StartDependencies;
|
||||
managerWithoutEmbeddable: UiActionsEnhancedDynamicActionManager;
|
||||
managerWithoutEmbeddableSingleButton: UiActionsEnhancedDynamicActionManager;
|
||||
managerWithEmbeddable: UiActionsEnhancedDynamicActionManager;
|
||||
}
|
||||
|
||||
export const context = createContext<UiActionsExampleAppContextValue | null>(null);
|
||||
export const useUiActions = () => useContext(context)!;
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { CollectConfigContainer } from './collect_config_container';
|
||||
export * from './context';
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public';
|
||||
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { SAMPLE_APP1_CLICK_TRIGGER, SampleApp1ClickContext } from '../../triggers';
|
||||
import { SerializableState } from '../../../../../../src/plugins/kibana_utils/common';
|
||||
|
||||
export interface Config extends SerializableState {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Trigger = typeof SAMPLE_APP1_CLICK_TRIGGER;
|
||||
type Context = SampleApp1ClickContext;
|
||||
|
||||
export type CollectConfigProps = CollectConfigPropsBase<Config, { triggers: Trigger[] }>;
|
||||
|
||||
export const APP1_HELLO_WORLD_DRILLDOWN = 'APP1_HELLO_WORLD_DRILLDOWN';
|
||||
|
||||
export class App1HelloWorldDrilldown implements Drilldown<Config, Trigger> {
|
||||
public readonly id = APP1_HELLO_WORLD_DRILLDOWN;
|
||||
|
||||
public readonly order = 8;
|
||||
|
||||
public readonly getDisplayName = () => 'Hello world (app 1)';
|
||||
|
||||
public readonly euiIcon = 'cheer';
|
||||
|
||||
supportedTriggers(): Trigger[] {
|
||||
return [SAMPLE_APP1_CLICK_TRIGGER];
|
||||
}
|
||||
|
||||
private readonly ReactCollectConfig: React.FC<CollectConfigProps> = ({
|
||||
config,
|
||||
onConfig,
|
||||
context,
|
||||
}) => (
|
||||
<EuiFormRow label="Enter your name" fullWidth>
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
value={config.name}
|
||||
onChange={(event) => onConfig({ ...config, name: event.target.value })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
|
||||
|
||||
public readonly createConfig = () => ({
|
||||
name: '',
|
||||
});
|
||||
|
||||
public readonly isConfigValid = (config: Config): config is Config => {
|
||||
return !!config.name;
|
||||
};
|
||||
|
||||
public readonly execute = async (config: Config, context: Context) => {
|
||||
alert(`Hello, ${config.name}`);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './app1_hello_world_drilldown';
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
DashboardEnhancedAbstractDashboardDrilldown as AbstractDashboardDrilldown,
|
||||
DashboardEnhancedAbstractDashboardDrilldownConfig as Config,
|
||||
} from '../../../../../plugins/dashboard_enhanced/public';
|
||||
import { SAMPLE_APP1_CLICK_TRIGGER, SampleApp1ClickContext } from '../../triggers';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
|
||||
export const APP1_TO_DASHBOARD_DRILLDOWN = 'APP1_TO_DASHBOARD_DRILLDOWN';
|
||||
|
||||
type Trigger = typeof SAMPLE_APP1_CLICK_TRIGGER;
|
||||
type Context = SampleApp1ClickContext;
|
||||
|
||||
export class App1ToDashboardDrilldown extends AbstractDashboardDrilldown<Trigger> {
|
||||
public readonly id = APP1_TO_DASHBOARD_DRILLDOWN;
|
||||
|
||||
public readonly supportedTriggers = () => [SAMPLE_APP1_CLICK_TRIGGER] as Trigger[];
|
||||
|
||||
protected async getURL(config: Config, context: Context): Promise<KibanaURL> {
|
||||
const path = await this.urlGenerator.createUrl({
|
||||
dashboardId: config.dashboardId,
|
||||
});
|
||||
const url = new KibanaURL(path);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './app1_to_dashboard_drilldown';
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
DashboardEnhancedAbstractDashboardDrilldown as AbstractDashboardDrilldown,
|
||||
DashboardEnhancedAbstractDashboardDrilldownConfig as Config,
|
||||
} from '../../../../../plugins/dashboard_enhanced/public';
|
||||
import { SAMPLE_APP2_CLICK_TRIGGER, SampleApp2ClickContext } from '../../triggers';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
|
||||
export const APP2_TO_DASHBOARD_DRILLDOWN = 'APP2_TO_DASHBOARD_DRILLDOWN';
|
||||
|
||||
type Trigger = typeof SAMPLE_APP2_CLICK_TRIGGER;
|
||||
type Context = SampleApp2ClickContext;
|
||||
|
||||
export class App2ToDashboardDrilldown extends AbstractDashboardDrilldown<Trigger> {
|
||||
public readonly id = APP2_TO_DASHBOARD_DRILLDOWN;
|
||||
|
||||
public readonly supportedTriggers = () => [SAMPLE_APP2_CLICK_TRIGGER] as Trigger[];
|
||||
|
||||
protected async getURL(config: Config, context: Context): Promise<KibanaURL> {
|
||||
const path = await this.urlGenerator.createUrl({
|
||||
dashboardId: config.dashboardId,
|
||||
});
|
||||
const url = new KibanaURL(path);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './app2_to_dashboard_drilldown';
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
|
||||
import { ChartActionContext } from '../../../../../src/plugins/embeddable/public';
|
||||
import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public';
|
||||
import { ChartActionContext } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import {
|
||||
SELECT_RANGE_TRIGGER,
|
||||
VALUE_CLICK_TRIGGER,
|
||||
} from '../../../../../src/plugins/ui_actions/public';
|
||||
} from '../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export type ActionContext = ChartActionContext;
|
||||
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
|
||||
import { RangeSelectContext } from '../../../../../src/plugins/embeddable/public';
|
||||
import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { SELECT_RANGE_TRIGGER } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { BaseActionFactoryContext } from '../../../../plugins/ui_actions_enhanced/public/dynamic_actions';
|
||||
import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public';
|
||||
import { RangeSelectContext } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { SELECT_RANGE_TRIGGER } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import { BaseActionFactoryContext } from '../../../../../plugins/ui_actions_enhanced/public/dynamic_actions';
|
||||
|
||||
export type Config = {
|
||||
name: string;
|
|
@ -5,15 +5,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { StartDependencies as Start } from '../plugin';
|
||||
import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { StartServicesGetter } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { StartDependencies as Start } from '../../plugin';
|
||||
import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { ActionContext, Config, CollectConfigProps } from './types';
|
||||
import { CollectConfigContainer } from './collect_config_container';
|
||||
import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public';
|
||||
import { txtGoToDiscover } from './i18n';
|
||||
import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
const isOutputWithIndexPatterns = (
|
||||
output: unknown
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { ApplyGlobalFilterActionContext } from '../../../../../src/plugins/data/public';
|
||||
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { ApplyGlobalFilterActionContext } from '../../../../../../src/plugins/data/public';
|
||||
|
||||
export type ActionContext = ApplyGlobalFilterActionContext;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createElement } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { AdvancedUiActionsStart } from '../../../../../plugins/ui_actions_enhanced/public';
|
||||
import { Embeddable, EmbeddableInput } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { ButtonEmbeddableComponent } from './button_embeddable_component';
|
||||
import { VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export const BUTTON_EMBEDDABLE = 'BUTTON_EMBEDDABLE';
|
||||
|
||||
export interface ButtonEmbeddableParams {
|
||||
uiActions: AdvancedUiActionsStart;
|
||||
}
|
||||
|
||||
export class ButtonEmbeddable extends Embeddable {
|
||||
type = BUTTON_EMBEDDABLE;
|
||||
|
||||
constructor(input: EmbeddableInput, private readonly params: ButtonEmbeddableParams) {
|
||||
super(input, {});
|
||||
}
|
||||
|
||||
reload() {}
|
||||
|
||||
private el?: HTMLElement;
|
||||
|
||||
public render(el: HTMLElement): void {
|
||||
super.render(el);
|
||||
this.el = el;
|
||||
render(
|
||||
createElement(ButtonEmbeddableComponent, {
|
||||
onClick: () => {
|
||||
this.params.uiActions.getTrigger(VALUE_CLICK_TRIGGER).exec({
|
||||
embeddable: this,
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
el
|
||||
);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
if (this.el) unmountComponentAtNode(this.el);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { EuiCard, EuiFlexItem, EuiIcon } from '@elastic/eui';
|
||||
|
||||
export interface ButtonEmbeddableComponentProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const ButtonEmbeddableComponent: React.FC<ButtonEmbeddableComponentProps> = ({
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="xxl" type={`logoKibana`} />}
|
||||
title={`Click me!`}
|
||||
description={'This embeddable fires "VALUE_CLICK" trigger on click'}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './button_embeddable';
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { CoreSetup, AppMountParameters } from 'kibana/public';
|
||||
import { StartDependencies, UiActionsEnhancedExamplesStart } from './plugin';
|
||||
import { UiActionsExampleAppContextValue, context } from './context';
|
||||
|
||||
export const mount = (
|
||||
coreSetup: CoreSetup<StartDependencies, UiActionsEnhancedExamplesStart>
|
||||
) => async ({ appBasePath, element }: AppMountParameters) => {
|
||||
const [
|
||||
core,
|
||||
plugins,
|
||||
{ managerWithoutEmbeddable, managerWithoutEmbeddableSingleButton, managerWithEmbeddable },
|
||||
] = await coreSetup.getStartServices();
|
||||
const { App } = await import('./containers/app');
|
||||
|
||||
const deps: UiActionsExampleAppContextValue = {
|
||||
appBasePath,
|
||||
core,
|
||||
plugins,
|
||||
managerWithoutEmbeddable,
|
||||
managerWithoutEmbeddableSingleButton,
|
||||
managerWithEmbeddable,
|
||||
};
|
||||
const reactElement = (
|
||||
<context.Provider value={deps}>
|
||||
<App />
|
||||
</context.Provider>
|
||||
);
|
||||
render(reactElement, element);
|
||||
return () => unmountComponentAtNode(element);
|
||||
};
|
|
@ -4,44 +4,174 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Plugin, CoreSetup, CoreStart } from '../../../../src/core/public';
|
||||
import { createElement as h } from 'react';
|
||||
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
|
||||
import { Plugin, CoreSetup, CoreStart, AppNavLinkStatus } from '../../../../src/core/public';
|
||||
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import {
|
||||
AdvancedUiActionsSetup,
|
||||
AdvancedUiActionsStart,
|
||||
} from '../../../../x-pack/plugins/ui_actions_enhanced/public';
|
||||
import { DashboardHelloWorldDrilldown } from './dashboard_hello_world_drilldown';
|
||||
import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown';
|
||||
import { DashboardHelloWorldDrilldown } from './drilldowns/dashboard_hello_world_drilldown';
|
||||
import { DashboardToDiscoverDrilldown } from './drilldowns/dashboard_to_discover_drilldown';
|
||||
import { App1ToDashboardDrilldown } from './drilldowns/app1_to_dashboard_drilldown';
|
||||
import { App1HelloWorldDrilldown } from './drilldowns/app1_hello_world_drilldown';
|
||||
import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public';
|
||||
import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public';
|
||||
import { DashboardHelloWorldOnlyRangeSelectDrilldown } from './dashboard_hello_world_only_range_select_drilldown';
|
||||
import { DashboardSetup, DashboardStart } from '../../../../src/plugins/dashboard/public';
|
||||
import { DashboardHelloWorldOnlyRangeSelectDrilldown } from './drilldowns/dashboard_hello_world_only_range_select_drilldown';
|
||||
import { DeveloperExamplesSetup } from '../../../../examples/developer_examples/public';
|
||||
import {
|
||||
sampleApp1ClickTrigger,
|
||||
sampleApp2ClickTrigger,
|
||||
SAMPLE_APP2_CLICK_TRIGGER,
|
||||
SampleApp2ClickContext,
|
||||
sampleApp2ClickContext,
|
||||
} from './triggers';
|
||||
import { mount } from './mount';
|
||||
import {
|
||||
UiActionsEnhancedMemoryActionStorage,
|
||||
UiActionsEnhancedDynamicActionManager,
|
||||
} from '../../../plugins/ui_actions_enhanced/public';
|
||||
import { App2ToDashboardDrilldown } from './drilldowns/app2_to_dashboard_drilldown';
|
||||
|
||||
export interface SetupDependencies {
|
||||
dashboard: DashboardSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
discover: DiscoverSetup;
|
||||
uiActionsEnhanced: AdvancedUiActionsSetup;
|
||||
}
|
||||
|
||||
export interface StartDependencies {
|
||||
dashboard: DashboardStart;
|
||||
data: DataPublicPluginStart;
|
||||
discover: DiscoverStart;
|
||||
uiActionsEnhanced: AdvancedUiActionsStart;
|
||||
}
|
||||
|
||||
export interface UiActionsEnhancedExamplesStart {
|
||||
managerWithoutEmbeddable: UiActionsEnhancedDynamicActionManager;
|
||||
managerWithoutEmbeddableSingleButton: UiActionsEnhancedDynamicActionManager;
|
||||
managerWithEmbeddable: UiActionsEnhancedDynamicActionManager;
|
||||
}
|
||||
|
||||
export class UiActionsEnhancedExamplesPlugin
|
||||
implements Plugin<void, void, SetupDependencies, StartDependencies> {
|
||||
implements Plugin<void, UiActionsEnhancedExamplesStart, SetupDependencies, StartDependencies> {
|
||||
public setup(
|
||||
core: CoreSetup<StartDependencies>,
|
||||
{ uiActionsEnhanced: uiActions }: SetupDependencies
|
||||
core: CoreSetup<StartDependencies, UiActionsEnhancedExamplesStart>,
|
||||
{ uiActionsEnhanced: uiActions, developerExamples }: SetupDependencies
|
||||
) {
|
||||
const start = createStartServicesGetter(core.getStartServices);
|
||||
|
||||
uiActions.registerDrilldown(new DashboardHelloWorldDrilldown());
|
||||
uiActions.registerDrilldown(new DashboardHelloWorldOnlyRangeSelectDrilldown());
|
||||
uiActions.registerDrilldown(new DashboardToDiscoverDrilldown({ start }));
|
||||
uiActions.registerDrilldown(new App1HelloWorldDrilldown());
|
||||
uiActions.registerDrilldown(new App1ToDashboardDrilldown({ start }));
|
||||
uiActions.registerDrilldown(new App2ToDashboardDrilldown({ start }));
|
||||
|
||||
uiActions.registerTrigger(sampleApp1ClickTrigger);
|
||||
uiActions.registerTrigger(sampleApp2ClickTrigger);
|
||||
|
||||
uiActions.addTriggerAction(SAMPLE_APP2_CLICK_TRIGGER, {
|
||||
id: 'SINGLE_ELEMENT_EXAMPLE_OPEN_FLYOUT_AT_CREATE',
|
||||
order: 2,
|
||||
getDisplayName: () => 'Add drilldown',
|
||||
getIconType: () => 'plusInCircle',
|
||||
isCompatible: async ({ workpadId, elementId }: SampleApp2ClickContext) =>
|
||||
workpadId === '123' && elementId === '456',
|
||||
execute: async () => {
|
||||
const { core: coreStart, plugins: pluginsStart, self } = start();
|
||||
const handle = coreStart.overlays.openFlyout(
|
||||
toMountPoint(
|
||||
h(pluginsStart.uiActionsEnhanced.FlyoutManageDrilldowns, {
|
||||
onClose: () => handle.close(),
|
||||
viewMode: 'create',
|
||||
dynamicActionManager: self.managerWithoutEmbeddableSingleButton,
|
||||
triggers: [SAMPLE_APP2_CLICK_TRIGGER],
|
||||
placeContext: {},
|
||||
})
|
||||
),
|
||||
{
|
||||
ownFocus: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
uiActions.addTriggerAction(SAMPLE_APP2_CLICK_TRIGGER, {
|
||||
id: 'SINGLE_ELEMENT_EXAMPLE_OPEN_FLYOUT_AT_MANAGE',
|
||||
order: 1,
|
||||
getDisplayName: () => 'Manage drilldowns',
|
||||
getIconType: () => 'list',
|
||||
isCompatible: async ({ workpadId, elementId }: SampleApp2ClickContext) =>
|
||||
workpadId === '123' && elementId === '456',
|
||||
execute: async () => {
|
||||
const { core: coreStart, plugins: pluginsStart, self } = start();
|
||||
const handle = coreStart.overlays.openFlyout(
|
||||
toMountPoint(
|
||||
h(pluginsStart.uiActionsEnhanced.FlyoutManageDrilldowns, {
|
||||
onClose: () => handle.close(),
|
||||
viewMode: 'manage',
|
||||
dynamicActionManager: self.managerWithoutEmbeddableSingleButton,
|
||||
triggers: [SAMPLE_APP2_CLICK_TRIGGER],
|
||||
placeContext: { sampleApp2ClickContext },
|
||||
})
|
||||
),
|
||||
{
|
||||
ownFocus: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
core.application.register({
|
||||
id: 'ui_actions_enhanced-explorer',
|
||||
title: 'UI Actions Enhanced Explorer',
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
mount: mount(core),
|
||||
});
|
||||
|
||||
developerExamples.register({
|
||||
appId: 'ui_actions_enhanced-explorer',
|
||||
title: 'UI Actions Enhanced',
|
||||
description: 'Examples of how to use drilldowns.',
|
||||
links: [
|
||||
{
|
||||
label: 'README',
|
||||
href:
|
||||
'https://github.com/elastic/kibana/tree/master/x-pack/examples/ui_actions_enhanced_examples#ui-actions-enhanced-examples',
|
||||
iconType: 'logoGithub',
|
||||
size: 's',
|
||||
target: '_blank',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: StartDependencies) {}
|
||||
public start(core: CoreStart, plugins: StartDependencies): UiActionsEnhancedExamplesStart {
|
||||
const managerWithoutEmbeddable = new UiActionsEnhancedDynamicActionManager({
|
||||
storage: new UiActionsEnhancedMemoryActionStorage(),
|
||||
isCompatible: async () => true,
|
||||
uiActions: plugins.uiActionsEnhanced,
|
||||
});
|
||||
const managerWithoutEmbeddableSingleButton = new UiActionsEnhancedDynamicActionManager({
|
||||
storage: new UiActionsEnhancedMemoryActionStorage(),
|
||||
isCompatible: async () => true,
|
||||
uiActions: plugins.uiActionsEnhanced,
|
||||
});
|
||||
const managerWithEmbeddable = new UiActionsEnhancedDynamicActionManager({
|
||||
storage: new UiActionsEnhancedMemoryActionStorage(),
|
||||
isCompatible: async () => true,
|
||||
uiActions: plugins.uiActionsEnhanced,
|
||||
});
|
||||
|
||||
return {
|
||||
managerWithoutEmbeddable,
|
||||
managerWithoutEmbeddableSingleButton,
|
||||
managerWithEmbeddable,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './sample_app1_trigger';
|
||||
export * from './sample_app2_trigger';
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Trigger } from '../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export const SAMPLE_APP1_CLICK_TRIGGER = 'SAMPLE_APP1_CLICK_TRIGGER';
|
||||
|
||||
export const sampleApp1ClickTrigger: Trigger<'SAMPLE_APP1_CLICK_TRIGGER'> = {
|
||||
id: SAMPLE_APP1_CLICK_TRIGGER,
|
||||
title: 'App 1 trigger fired on click',
|
||||
description: 'Could be a click on a ML job in ML app.',
|
||||
};
|
||||
|
||||
declare module '../../../../../src/plugins/ui_actions/public' {
|
||||
export interface TriggerContextMapping {
|
||||
[SAMPLE_APP1_CLICK_TRIGGER]: SampleApp1ClickContext;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SampleApp1ClickContext {
|
||||
job: SampleMlJob;
|
||||
}
|
||||
|
||||
export interface SampleMlJob {
|
||||
job_id: string;
|
||||
job_type: 'anomaly_detector';
|
||||
description: string;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Trigger } from '../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export const SAMPLE_APP2_CLICK_TRIGGER = 'SAMPLE_APP2_CLICK_TRIGGER';
|
||||
|
||||
export const sampleApp2ClickTrigger: Trigger<'SAMPLE_APP2_CLICK_TRIGGER'> = {
|
||||
id: SAMPLE_APP2_CLICK_TRIGGER,
|
||||
title: 'App 2 trigger fired on click',
|
||||
description: 'Could be a click on an element in Canvas app.',
|
||||
};
|
||||
|
||||
declare module '../../../../../src/plugins/ui_actions/public' {
|
||||
export interface TriggerContextMapping {
|
||||
[SAMPLE_APP2_CLICK_TRIGGER]: SampleApp2ClickContext;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SampleApp2ClickContext {
|
||||
workpadId: string;
|
||||
elementId: string;
|
||||
}
|
||||
|
||||
export const sampleApp2ClickContext: SampleApp2ClickContext = {
|
||||
workpadId: '123',
|
||||
elementId: '456',
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
"requiredPlugins": ["data", "uiActionsEnhanced", "embeddable", "dashboard", "share"],
|
||||
"configPath": ["xpack", "dashboardEnhanced"],
|
||||
"requiredBundles": [
|
||||
"embeddable",
|
||||
"kibanaUtils",
|
||||
"embeddableEnhanced",
|
||||
"kibanaReact",
|
||||
|
|
|
@ -14,6 +14,12 @@ export {
|
|||
StartDependencies as DashboardEnhancedStartDependencies,
|
||||
} from './plugin';
|
||||
|
||||
export {
|
||||
AbstractDashboardDrilldown as DashboardEnhancedAbstractDashboardDrilldown,
|
||||
AbstractDashboardDrilldownConfig as DashboardEnhancedAbstractDashboardDrilldownConfig,
|
||||
AbstractDashboardDrilldownParams as DashboardEnhancedAbstractDashboardDrilldownParams,
|
||||
} from './services/drilldowns/abstract_dashboard_drilldown';
|
||||
|
||||
export function plugin(context: PluginInitializerContext) {
|
||||
return new DashboardEnhancedPlugin(context);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
import { DashboardStart } from 'src/plugins/dashboard/public';
|
||||
import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import {
|
||||
TriggerId,
|
||||
TriggerContextMapping,
|
||||
} from '../../../../../../../src/plugins/ui_actions/public';
|
||||
import { CollectConfigContainer } from './components';
|
||||
import {
|
||||
UiActionsEnhancedDrilldownDefinition as Drilldown,
|
||||
UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext,
|
||||
AdvancedUiActionsStart,
|
||||
} from '../../../../../ui_actions_enhanced/public';
|
||||
import { txtGoToDashboard } from './i18n';
|
||||
import {
|
||||
StartServicesGetter,
|
||||
CollectConfigProps,
|
||||
} from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { KibanaURL } from '../../../../../../../src/plugins/share/public';
|
||||
import { Config } from './types';
|
||||
|
||||
export interface Params {
|
||||
start: StartServicesGetter<{
|
||||
uiActionsEnhanced: AdvancedUiActionsStart;
|
||||
data: DataPublicPluginStart;
|
||||
dashboard: DashboardStart;
|
||||
}>;
|
||||
}
|
||||
|
||||
export abstract class AbstractDashboardDrilldown<T extends TriggerId>
|
||||
implements Drilldown<Config, T, BaseActionFactoryContext<T>> {
|
||||
constructor(protected readonly params: Params) {}
|
||||
|
||||
public abstract readonly id: string;
|
||||
|
||||
public abstract readonly supportedTriggers: () => T[];
|
||||
|
||||
protected abstract getURL(config: Config, context: TriggerContextMapping[T]): Promise<KibanaURL>;
|
||||
|
||||
public readonly order = 100;
|
||||
|
||||
public readonly getDisplayName = () => txtGoToDashboard;
|
||||
|
||||
public readonly euiIcon = 'dashboardApp';
|
||||
|
||||
private readonly ReactCollectConfig: React.FC<
|
||||
CollectConfigProps<Config, BaseActionFactoryContext<T>>
|
||||
> = (props) => <CollectConfigContainer {...props} params={this.params} />;
|
||||
|
||||
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
|
||||
|
||||
public readonly createConfig = () => ({
|
||||
dashboardId: '',
|
||||
useCurrentFilters: true,
|
||||
useCurrentDateRange: true,
|
||||
});
|
||||
|
||||
public readonly isConfigValid = (config: Config): config is Config => {
|
||||
if (!config.dashboardId) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
public readonly getHref = async (
|
||||
config: Config,
|
||||
context: TriggerContextMapping[T]
|
||||
): Promise<string> => {
|
||||
const url = await this.getURL(config, context);
|
||||
return url.path;
|
||||
};
|
||||
|
||||
public readonly execute = async (config: Config, context: TriggerContextMapping[T]) => {
|
||||
const url = await this.getURL(config, context);
|
||||
await this.params.start().core.application.navigateToApp(url.appName, { path: url.appPath });
|
||||
};
|
||||
|
||||
protected get urlGenerator() {
|
||||
const urlGenerator = this.params.start().plugins.dashboard.dashboardUrlGenerator;
|
||||
if (!urlGenerator)
|
||||
throw new Error('Dashboard URL generator is required for dashboard drilldown.');
|
||||
return urlGenerator;
|
||||
}
|
||||
}
|
|
@ -11,8 +11,8 @@ import { SimpleSavedObject } from '../../../../../../../../src/core/public';
|
|||
import { DashboardDrilldownConfig } from './dashboard_drilldown_config';
|
||||
import { txtDestinationDashboardNotFound } from './i18n';
|
||||
import { CollectConfigProps } from '../../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { Config, FactoryContext } from '../types';
|
||||
import { Params } from '../drilldown';
|
||||
import { Config } from '../types';
|
||||
import { Params } from '../abstract_dashboard_drilldown';
|
||||
|
||||
const mergeDashboards = (
|
||||
dashboards: Array<EuiComboBoxOptionOption<string>>,
|
||||
|
@ -34,7 +34,7 @@ const dashboardSavedObjectToMenuItem = (
|
|||
label: savedObject.attributes.title,
|
||||
});
|
||||
|
||||
interface DashboardDrilldownCollectConfigProps extends CollectConfigProps<Config, FactoryContext> {
|
||||
export interface DashboardDrilldownCollectConfigProps extends CollectConfigProps<Config, object> {
|
||||
params: Params;
|
||||
}
|
||||
|
|
@ -4,9 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants';
|
||||
export {
|
||||
DashboardToDashboardDrilldown,
|
||||
Params as DashboardToDashboardDrilldownParams,
|
||||
} from './drilldown';
|
||||
export { Config } from './types';
|
||||
CollectConfigContainer,
|
||||
DashboardDrilldownCollectConfigProps,
|
||||
} from './collect_config_container';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export {
|
||||
AbstractDashboardDrilldown,
|
||||
Params as AbstractDashboardDrilldownParams,
|
||||
} from './abstract_dashboard_drilldown';
|
||||
export { Config as AbstractDashboardDrilldownConfig } from './types';
|
|
@ -83,7 +83,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType<typeof OPEN_FLY
|
|||
onClose={() => handle.close()}
|
||||
viewMode={'create'}
|
||||
dynamicActionManager={embeddable.enhancements.dynamicActions}
|
||||
supportedTriggers={ensureNestedTriggers(embeddable.supportedTriggers())}
|
||||
triggers={ensureNestedTriggers(embeddable.supportedTriggers())}
|
||||
placeContext={{ embeddable }}
|
||||
/>
|
||||
),
|
||||
|
|
|
@ -67,7 +67,7 @@ export class FlyoutEditDrilldownAction implements ActionByType<typeof OPEN_FLYOU
|
|||
onClose={() => handle.close()}
|
||||
viewMode={'manage'}
|
||||
dynamicActionManager={embeddable.enhancements.dynamicActions}
|
||||
supportedTriggers={ensureNestedTriggers(embeddable.supportedTriggers())}
|
||||
triggers={ensureNestedTriggers(embeddable.supportedTriggers())}
|
||||
placeContext={{ embeddable }}
|
||||
/>
|
||||
),
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
OPEN_FLYOUT_ADD_DRILLDOWN,
|
||||
OPEN_FLYOUT_EDIT_DRILLDOWN,
|
||||
} from './actions';
|
||||
import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldown';
|
||||
import { EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown';
|
||||
import { createStartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
declare module '../../../../../../src/plugins/ui_actions/public' {
|
||||
|
@ -44,12 +44,6 @@ export class DashboardDrilldownsService {
|
|||
{ uiActionsEnhanced: uiActions }: SetupDependencies
|
||||
) {
|
||||
const start = createStartServicesGetter(core.getStartServices);
|
||||
const getDashboardUrlGenerator = () => {
|
||||
const urlGenerator = start().plugins.dashboard.dashboardUrlGenerator;
|
||||
if (!urlGenerator)
|
||||
throw new Error('dashboardUrlGenerator is required for dashboard to dashboard drilldown');
|
||||
return urlGenerator;
|
||||
};
|
||||
|
||||
const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ start });
|
||||
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown);
|
||||
|
@ -57,10 +51,7 @@ export class DashboardDrilldownsService {
|
|||
const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ start });
|
||||
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown);
|
||||
|
||||
const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({
|
||||
start,
|
||||
getDashboardUrlGenerator,
|
||||
});
|
||||
const dashboardToDashboardDrilldown = new EmbeddableToDashboardDrilldown({ start });
|
||||
uiActions.registerDrilldown(dashboardToDashboardDrilldown);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/ui_actions/public';
|
||||
import {
|
||||
DashboardUrlGenerator,
|
||||
DashboardUrlGeneratorState,
|
||||
} from '../../../../../../../src/plugins/dashboard/public';
|
||||
import { CollectConfigContainer } from './components';
|
||||
import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants';
|
||||
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../ui_actions_enhanced/public';
|
||||
import { txtGoToDashboard } from './i18n';
|
||||
import {
|
||||
ApplyGlobalFilterActionContext,
|
||||
esFilters,
|
||||
isFilters,
|
||||
isQuery,
|
||||
isTimeRange,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { StartDependencies } from '../../../plugin';
|
||||
import { Config, FactoryContext } from './types';
|
||||
import { SearchInput } from '../../../../../../../src/plugins/discover/public';
|
||||
|
||||
export interface Params {
|
||||
start: StartServicesGetter<Pick<StartDependencies, 'data' | 'uiActionsEnhanced'>>;
|
||||
getDashboardUrlGenerator: () => DashboardUrlGenerator;
|
||||
}
|
||||
|
||||
export class DashboardToDashboardDrilldown
|
||||
implements Drilldown<Config, typeof APPLY_FILTER_TRIGGER, FactoryContext> {
|
||||
constructor(protected readonly params: Params) {}
|
||||
|
||||
public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN;
|
||||
|
||||
public readonly order = 100;
|
||||
|
||||
public readonly getDisplayName = () => txtGoToDashboard;
|
||||
|
||||
public readonly euiIcon = 'dashboardApp';
|
||||
|
||||
private readonly ReactCollectConfig: React.FC<CollectConfigContainer['props']> = (props) => (
|
||||
<CollectConfigContainer {...props} params={this.params} />
|
||||
);
|
||||
|
||||
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
|
||||
|
||||
public readonly createConfig = () => ({
|
||||
dashboardId: '',
|
||||
useCurrentFilters: true,
|
||||
useCurrentDateRange: true,
|
||||
});
|
||||
|
||||
public readonly isConfigValid = (config: Config): config is Config => {
|
||||
if (!config.dashboardId) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
public supportedTriggers(): Array<typeof APPLY_FILTER_TRIGGER> {
|
||||
return [APPLY_FILTER_TRIGGER];
|
||||
}
|
||||
|
||||
public readonly getHref = async (
|
||||
config: Config,
|
||||
context: ApplyGlobalFilterActionContext
|
||||
): Promise<string> => {
|
||||
return this.getDestinationUrl(config, context);
|
||||
};
|
||||
|
||||
public readonly execute = async (config: Config, context: ApplyGlobalFilterActionContext) => {
|
||||
const dashboardPath = await this.getDestinationUrl(config, context);
|
||||
const dashboardHash = dashboardPath.split('#')[1];
|
||||
|
||||
await this.params.start().core.application.navigateToApp('dashboards', {
|
||||
path: `#${dashboardHash}`,
|
||||
});
|
||||
};
|
||||
|
||||
private getDestinationUrl = async (
|
||||
config: Config,
|
||||
context: ApplyGlobalFilterActionContext
|
||||
): Promise<string> => {
|
||||
const state: DashboardUrlGeneratorState = {
|
||||
dashboardId: config.dashboardId,
|
||||
};
|
||||
|
||||
if (context.embeddable) {
|
||||
const input = context.embeddable.getInput() as Readonly<SearchInput>;
|
||||
if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query;
|
||||
|
||||
// if useCurrentDashboardDataRange is enabled, then preserve current time range
|
||||
// if undefined is passed, then destination dashboard will figure out time range itself
|
||||
// for brush event this time range would be overwritten
|
||||
if (isTimeRange(input.timeRange) && config.useCurrentDateRange)
|
||||
state.timeRange = input.timeRange;
|
||||
|
||||
// if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned)
|
||||
// otherwise preserve only pinned
|
||||
if (isFilters(input.filters))
|
||||
state.filters = config.useCurrentFilters
|
||||
? input.filters
|
||||
: input.filters?.filter((f) => esFilters.isFilterPinned(f));
|
||||
}
|
||||
|
||||
const {
|
||||
restOfFilters: filtersFromEvent,
|
||||
timeRange: timeRangeFromEvent,
|
||||
} = esFilters.extractTimeRange(context.filters, context.timeFieldName);
|
||||
|
||||
if (filtersFromEvent) {
|
||||
state.filters = [...(state.filters ?? []), ...filtersFromEvent];
|
||||
}
|
||||
|
||||
if (timeRangeFromEvent) {
|
||||
state.timeRange = timeRangeFromEvent;
|
||||
}
|
||||
|
||||
return this.params.getDashboardUrlGenerator().createUrl(state);
|
||||
};
|
||||
}
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* note:
|
||||
* don't change this string without carefull consideration,
|
||||
* because it is stored in saved objects.
|
||||
* NOTE: DO NOT CHANGE THIS STRING WITHOUT CAREFUL CONSIDERATOIN, BECAUSE IT IS
|
||||
* STORED IN SAVED OBJECTS.
|
||||
*
|
||||
* Also temporary dashboard drilldown migration code inside embeddable plugin relies on it
|
||||
* x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts
|
||||
*/
|
||||
export const DASHBOARD_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN';
|
||||
export const EMBEDDABLE_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN';
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { DashboardToDashboardDrilldown } from './drilldown';
|
||||
import { Config } from './types';
|
||||
import { EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown';
|
||||
import { AbstractDashboardDrilldownConfig as Config } from '../abstract_dashboard_drilldown';
|
||||
import { coreMock, savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks';
|
||||
import {
|
||||
Filter,
|
||||
|
@ -18,7 +18,6 @@ import {
|
|||
ApplyGlobalFilterActionContext,
|
||||
esFilters,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
// convenient to use real implementation here.
|
||||
import { createDashboardUrlGenerator } from '../../../../../../../src/plugins/dashboard/public/url_generator';
|
||||
import { UrlGeneratorsService } from '../../../../../../../src/plugins/share/public/url_generators';
|
||||
import { StartDependencies } from '../../../plugin';
|
||||
|
@ -26,7 +25,7 @@ import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_object
|
|||
import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public/core';
|
||||
|
||||
describe('.isConfigValid()', () => {
|
||||
const drilldown = new DashboardToDashboardDrilldown({} as any);
|
||||
const drilldown = new EmbeddableToDashboardDrilldown({} as any);
|
||||
|
||||
test('returns false for invalid config with missing dashboard id', () => {
|
||||
expect(
|
||||
|
@ -50,19 +49,19 @@ describe('.isConfigValid()', () => {
|
|||
});
|
||||
|
||||
test('config component exist', () => {
|
||||
const drilldown = new DashboardToDashboardDrilldown({} as any);
|
||||
const drilldown = new EmbeddableToDashboardDrilldown({} as any);
|
||||
expect(drilldown.CollectConfig).toEqual(expect.any(Function));
|
||||
});
|
||||
|
||||
test('initial config: switches are ON', () => {
|
||||
const drilldown = new DashboardToDashboardDrilldown({} as any);
|
||||
const drilldown = new EmbeddableToDashboardDrilldown({} as any);
|
||||
const { useCurrentDateRange, useCurrentFilters } = drilldown.createConfig();
|
||||
expect(useCurrentDateRange).toBe(true);
|
||||
expect(useCurrentFilters).toBe(true);
|
||||
});
|
||||
|
||||
test('getHref is defined', () => {
|
||||
const drilldown = new DashboardToDashboardDrilldown({} as any);
|
||||
const drilldown = new EmbeddableToDashboardDrilldown({} as any);
|
||||
expect(drilldown.getHref).toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -84,7 +83,7 @@ describe('.execute() & getHref', () => {
|
|||
const getUrlForApp = jest.fn((app, opt) => `${app}/${opt.path}`);
|
||||
const savedObjectsClient = savedObjectsServiceMock.createStartContract().client;
|
||||
|
||||
const drilldown = new DashboardToDashboardDrilldown({
|
||||
const drilldown = new EmbeddableToDashboardDrilldown({
|
||||
start: ((() => ({
|
||||
core: {
|
||||
application: {
|
||||
|
@ -97,19 +96,24 @@ describe('.execute() & getHref', () => {
|
|||
},
|
||||
plugins: {
|
||||
uiActionsEnhanced: {},
|
||||
dashboard: {
|
||||
dashboardUrlGenerator: new UrlGeneratorsService()
|
||||
.setup(coreMock.createSetup())
|
||||
.registerUrlGenerator(
|
||||
createDashboardUrlGenerator(() =>
|
||||
Promise.resolve({
|
||||
appBasePath: 'xyz/app/dashboards',
|
||||
useHashedUrl: false,
|
||||
savedDashboardLoader: ({} as unknown) as SavedObjectLoader,
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
},
|
||||
self: {},
|
||||
})) as unknown) as StartServicesGetter<Pick<StartDependencies, 'data' | 'uiActionsEnhanced'>>,
|
||||
getDashboardUrlGenerator: () =>
|
||||
new UrlGeneratorsService().setup(coreMock.createSetup()).registerUrlGenerator(
|
||||
createDashboardUrlGenerator(() =>
|
||||
Promise.resolve({
|
||||
appBasePath: 'test',
|
||||
useHashedUrl: false,
|
||||
savedDashboardLoader: ({} as unknown) as SavedObjectLoader,
|
||||
})
|
||||
)
|
||||
),
|
||||
})) as unknown) as StartServicesGetter<
|
||||
Pick<StartDependencies, 'data' | 'uiActionsEnhanced' | 'dashboard'>
|
||||
>,
|
||||
});
|
||||
|
||||
const completeConfig: Config = {
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
TriggerContextMapping,
|
||||
APPLY_FILTER_TRIGGER,
|
||||
} from '../../../../../../../src/plugins/ui_actions/public';
|
||||
import { DashboardUrlGeneratorState } from '../../../../../../../src/plugins/dashboard/public';
|
||||
import {
|
||||
esFilters,
|
||||
isFilters,
|
||||
isQuery,
|
||||
isTimeRange,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
AbstractDashboardDrilldown,
|
||||
AbstractDashboardDrilldownParams,
|
||||
AbstractDashboardDrilldownConfig as Config,
|
||||
} from '../abstract_dashboard_drilldown';
|
||||
import { KibanaURL } from '../../../../../../../src/plugins/share/public';
|
||||
import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants';
|
||||
|
||||
type Trigger = typeof APPLY_FILTER_TRIGGER;
|
||||
type Context = TriggerContextMapping[Trigger];
|
||||
export type Params = AbstractDashboardDrilldownParams;
|
||||
|
||||
/**
|
||||
* This drilldown is the "Go to Dashboard" you can find in Dashboard app panles.
|
||||
* This drilldown can be used on any embeddable and it is tied to embeddables
|
||||
* in two ways: (1) it works with APPLY_FILTER_TRIGGER, which is usually executed
|
||||
* by embeddables (but not necessarily); (2) its `getURL` method depends on
|
||||
* `embeddable` field being present in `context`.
|
||||
*/
|
||||
export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown<Trigger> {
|
||||
public readonly id = EMBEDDABLE_TO_DASHBOARD_DRILLDOWN;
|
||||
|
||||
public readonly supportedTriggers = () => [APPLY_FILTER_TRIGGER] as Trigger[];
|
||||
|
||||
protected async getURL(config: Config, context: Context): Promise<KibanaURL> {
|
||||
const state: DashboardUrlGeneratorState = {
|
||||
dashboardId: config.dashboardId,
|
||||
};
|
||||
|
||||
if (context.embeddable) {
|
||||
const input = context.embeddable.getInput();
|
||||
if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query;
|
||||
|
||||
// if useCurrentDashboardDataRange is enabled, then preserve current time range
|
||||
// if undefined is passed, then destination dashboard will figure out time range itself
|
||||
// for brush event this time range would be overwritten
|
||||
if (isTimeRange(input.timeRange) && config.useCurrentDateRange)
|
||||
state.timeRange = input.timeRange;
|
||||
|
||||
// if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned)
|
||||
// otherwise preserve only pinned
|
||||
if (isFilters(input.filters))
|
||||
state.filters = config.useCurrentFilters
|
||||
? input.filters
|
||||
: input.filters?.filter((f) => esFilters.isFilterPinned(f));
|
||||
}
|
||||
|
||||
const {
|
||||
restOfFilters: filtersFromEvent,
|
||||
timeRange: timeRangeFromEvent,
|
||||
} = esFilters.extractTimeRange(context.filters, context.timeFieldName);
|
||||
|
||||
if (filtersFromEvent) {
|
||||
state.filters = [...(state.filters ?? []), ...filtersFromEvent];
|
||||
}
|
||||
|
||||
if (timeRangeFromEvent) {
|
||||
state.timeRange = timeRangeFromEvent;
|
||||
}
|
||||
|
||||
const path = await this.urlGenerator.createUrl(state);
|
||||
const url = new KibanaURL(path);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants';
|
||||
export {
|
||||
EmbeddableToDashboardDrilldown,
|
||||
Params as EmbeddableToDashboardDrilldownParams,
|
||||
} from './embeddable_to_dashboard_drilldown';
|
|
@ -7,5 +7,5 @@
|
|||
"requiredPlugins": ["uiActions", "embeddable", "discover"],
|
||||
"optionalPlugins": ["share", "kibanaLegacy", "usageCollection"],
|
||||
"configPath": ["xpack", "discoverEnhanced"],
|
||||
"requiredBundles": ["kibanaUtils", "data"]
|
||||
"requiredBundles": ["kibanaUtils", "data", "share"]
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ViewMode, IEmbeddable } from '../../../../../../src/plugins/embeddable/
|
|||
import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { KibanaLegacyStart } from '../../../../../../src/plugins/kibana_legacy/public';
|
||||
import { CoreStart } from '../../../../../../src/core/public';
|
||||
import { KibanaURL } from './kibana_url';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
import * as shared from './shared';
|
||||
|
||||
export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA';
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
ApplyGlobalFilterActionContext,
|
||||
esFilters,
|
||||
} from '../../../../../../src/plugins/data/public';
|
||||
import { KibanaURL } from './kibana_url';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
import * as shared from './shared';
|
||||
import { AbstractExploreDataAction } from './abstract_explore_data_action';
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { Action } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import { EmbeddableContext } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { DiscoverUrlGeneratorState } from '../../../../../../src/plugins/discover/public';
|
||||
import { KibanaURL } from './kibana_url';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
import * as shared from './shared';
|
||||
import { AbstractExploreDataAction } from './abstract_explore_data_action';
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// TODO: Replace this logic with KibanaURL once it is available.
|
||||
// https://github.com/elastic/kibana/issues/64497
|
||||
export class KibanaURL {
|
||||
public readonly path: string;
|
||||
public readonly appName: string;
|
||||
public readonly appPath: string;
|
||||
|
||||
constructor(path: string) {
|
||||
const match = path.match(/^.*\/app\/([^\/#]+)(.+)$/);
|
||||
|
||||
if (!match) {
|
||||
throw new Error('Unexpected Discover URL path.');
|
||||
}
|
||||
|
||||
const [, appName, appPath] = match;
|
||||
|
||||
if (!appName || !appPath) {
|
||||
throw new Error('Could not parse Discover URL path.');
|
||||
}
|
||||
|
||||
this.path = path;
|
||||
this.appName = appName;
|
||||
this.appPath = appPath;
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import {
|
|||
import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public';
|
||||
import { of } from '../../../../../src/plugins/kibana_utils/public';
|
||||
// use real const to make test fail in case someone accidentally changes it
|
||||
import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from '../../../dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown';
|
||||
import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
class TestEmbeddable extends Embeddable<EmbeddableWithDynamicActionsInput> {
|
||||
|
@ -555,7 +554,7 @@ describe('EmbeddableActionStorage', () => {
|
|||
eventId: '1',
|
||||
triggers: [OTHER_TRIGGER],
|
||||
action: {
|
||||
factoryId: DASHBOARD_TO_DASHBOARD_DRILLDOWN,
|
||||
factoryId: 'DASHBOARD_TO_DASHBOARD_DRILLDOWN',
|
||||
name: '',
|
||||
config: {},
|
||||
},
|
||||
|
|
|
@ -80,7 +80,7 @@ export interface ActionWizardProps<
|
|||
/**
|
||||
* List of possible triggers in current context
|
||||
*/
|
||||
supportedTriggers: TriggerId[];
|
||||
triggers: TriggerId[];
|
||||
|
||||
triggerPickerDocsLink?: string;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ export const ActionWizard: React.FC<ActionWizardProps> = ({
|
|||
context,
|
||||
onSelectedTriggersChange,
|
||||
getTriggerInfo,
|
||||
supportedTriggers,
|
||||
triggers,
|
||||
triggerPickerDocsLink,
|
||||
}) => {
|
||||
// auto pick action factory if there is only 1 available
|
||||
|
@ -108,14 +108,14 @@ export const ActionWizard: React.FC<ActionWizardProps> = ({
|
|||
|
||||
// auto pick selected trigger if none is picked
|
||||
if (currentActionFactory && !((context.triggers?.length ?? 0) > 0)) {
|
||||
const triggers = getTriggersForActionFactory(currentActionFactory, supportedTriggers);
|
||||
if (triggers.length > 0) {
|
||||
onSelectedTriggersChange([triggers[0]]);
|
||||
const actionTriggers = getTriggersForActionFactory(currentActionFactory, triggers);
|
||||
if (actionTriggers.length > 0) {
|
||||
onSelectedTriggersChange([actionTriggers[0]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentActionFactory && config) {
|
||||
const allTriggers = getTriggersForActionFactory(currentActionFactory, supportedTriggers);
|
||||
const allTriggers = getTriggersForActionFactory(currentActionFactory, triggers);
|
||||
return (
|
||||
<SelectedActionFactory
|
||||
actionFactory={currentActionFactory}
|
||||
|
|
|
@ -254,7 +254,7 @@ export function Demo({ actionFactories }: { actionFactories: Array<ActionFactory
|
|||
});
|
||||
}}
|
||||
getTriggerInfo={mockGetTriggerInfo}
|
||||
supportedTriggers={[VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER]}
|
||||
triggers={[VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER]}
|
||||
/>
|
||||
<div style={{ marginTop: '44px' }} />
|
||||
<hr />
|
||||
|
|
|
@ -34,7 +34,7 @@ storiesOf('components/FlyoutManageDrilldowns', module)
|
|||
<EuiFlyout onClose={() => {}}>
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={['VALUE_CLICK_TRIGGER', 'SELECT_RANGE_TRIGGER', 'FILTER_TRIGGER']}
|
||||
triggers={['VALUE_CLICK_TRIGGER', 'SELECT_RANGE_TRIGGER', 'FILTER_TRIGGER']}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
))
|
||||
|
@ -42,7 +42,7 @@ storiesOf('components/FlyoutManageDrilldowns', module)
|
|||
<EuiFlyout onClose={() => {}}>
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={['FILTER_TRIGGER']}
|
||||
triggers={['FILTER_TRIGGER']}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
));
|
||||
|
|
|
@ -19,7 +19,7 @@ import { TEST_SUBJ_DRILLDOWN_ITEM } from '../list_manage_drilldowns';
|
|||
import { WELCOME_MESSAGE_TEST_SUBJ } from '../drilldown_hello_bar';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { NotificationsStart } from 'kibana/public';
|
||||
import { toastDrilldownsCRUDError } from './i18n';
|
||||
import { toastDrilldownsCRUDError } from '../../hooks/i18n';
|
||||
|
||||
const storage = new Storage(new StubBrowserStorage());
|
||||
const toasts = coreMock.createStart().notifications.toasts;
|
||||
|
@ -41,7 +41,7 @@ test('Allows to manage drilldowns', async () => {
|
|||
const screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -115,7 +115,7 @@ test('Can delete multiple drilldowns', async () => {
|
|||
const screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
// wait for initial render. It is async because resolving compatible action factories is async
|
||||
|
@ -157,7 +157,7 @@ test('Create only mode', async () => {
|
|||
dynamicActionManager={mockDynamicActionManager}
|
||||
viewMode={'create'}
|
||||
onClose={onClose}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
// wait for initial render. It is async because resolving compatible action factories is async
|
||||
|
@ -181,7 +181,7 @@ test('After switching between action factories state is restored', async () => {
|
|||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
viewMode={'create'}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
// wait for initial render. It is async because resolving compatible action factories is async
|
||||
|
@ -222,7 +222,7 @@ test("Error when can't save drilldown changes", async () => {
|
|||
const screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
// wait for initial render. It is async because resolving compatible action factories is async
|
||||
|
@ -245,7 +245,7 @@ test('Should show drilldown welcome message. Should be able to dismiss it', asyn
|
|||
let screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -260,7 +260,7 @@ test('Should show drilldown welcome message. Should be able to dismiss it', asyn
|
|||
screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
/>
|
||||
);
|
||||
// wait for initial render. It is async because resolving compatible action factories is async
|
||||
|
@ -272,7 +272,7 @@ test('Drilldown type is not shown if no supported trigger', async () => {
|
|||
const screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={['VALUE_CLICK_TRIGGER']}
|
||||
triggers={['VALUE_CLICK_TRIGGER']}
|
||||
viewMode={'create'}
|
||||
/>
|
||||
);
|
||||
|
@ -286,7 +286,7 @@ test('Can pick a trigger', async () => {
|
|||
const screen = render(
|
||||
<FlyoutManageDrilldowns
|
||||
dynamicActionManager={mockDynamicActionManager}
|
||||
supportedTriggers={mockSupportedTriggers}
|
||||
triggers={mockSupportedTriggers}
|
||||
viewMode={'create'}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -4,33 +4,25 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { ToastsStart } from 'kibana/public';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import { intersection } from 'lodash';
|
||||
import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard';
|
||||
import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns';
|
||||
import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { Trigger, TriggerId } from '../../../../../../../src/plugins/ui_actions/public';
|
||||
import { useContainerState } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { DrilldownListItem } from '../list_manage_drilldowns';
|
||||
import {
|
||||
insufficientLicenseLevel,
|
||||
invalidDrilldownType,
|
||||
toastDrilldownCreated,
|
||||
toastDrilldownDeleted,
|
||||
toastDrilldownEdited,
|
||||
toastDrilldownsCRUDError,
|
||||
toastDrilldownsDeleted,
|
||||
} from './i18n';
|
||||
import { insufficientLicenseLevel, invalidDrilldownType } from './i18n';
|
||||
import {
|
||||
ActionFactory,
|
||||
BaseActionConfig,
|
||||
BaseActionFactoryContext,
|
||||
DynamicActionManager,
|
||||
SerializedAction,
|
||||
SerializedEvent,
|
||||
} from '../../../dynamic_actions';
|
||||
import { useWelcomeMessage } from '../../hooks/use_welcome_message';
|
||||
import { useCompatibleActionFactoriesForCurrentContext } from '../../hooks/use_compatible_action_factories_for_current_context';
|
||||
import { useDrilldownsStateManager } from '../../hooks/use_drilldown_state_manager';
|
||||
import { ActionFactoryPlaceContext } from '../types';
|
||||
|
||||
interface ConnectedFlyoutManageDrilldownsProps<
|
||||
|
@ -43,7 +35,7 @@ interface ConnectedFlyoutManageDrilldownsProps<
|
|||
/**
|
||||
* List of possible triggers in current context
|
||||
*/
|
||||
supportedTriggers: TriggerId[];
|
||||
triggers: TriggerId[];
|
||||
|
||||
/**
|
||||
* Extra action factory context passed into action factories CollectConfig, getIconType, getDisplayName and etc...
|
||||
|
@ -74,7 +66,7 @@ export function createFlyoutManageDrilldowns({
|
|||
toastService: ToastsStart;
|
||||
docsLink?: string;
|
||||
triggerPickerDocsLink?: string;
|
||||
}) {
|
||||
}): React.FC<ConnectedFlyoutManageDrilldownsProps> {
|
||||
const allActionFactoriesById = allActionFactories.reduce((acc, next) => {
|
||||
acc[next.id] = next;
|
||||
return acc;
|
||||
|
@ -84,8 +76,8 @@ export function createFlyoutManageDrilldowns({
|
|||
const isCreateOnly = props.viewMode === 'create';
|
||||
|
||||
const factoryContext: BaseActionFactoryContext = useMemo(
|
||||
() => ({ ...props.placeContext, triggers: props.supportedTriggers }),
|
||||
[props.placeContext, props.supportedTriggers]
|
||||
() => ({ ...props.placeContext, triggers: props.triggers }),
|
||||
[props.placeContext, props.triggers]
|
||||
);
|
||||
const actionFactories = useCompatibleActionFactoriesForCurrentContext(
|
||||
allActionFactories,
|
||||
|
@ -210,7 +202,7 @@ export function createFlyoutManageDrilldowns({
|
|||
}}
|
||||
actionFactoryPlaceContext={props.placeContext}
|
||||
initialDrilldownWizardConfig={resolveInitialDrilldownWizardConfig()}
|
||||
supportedTriggers={props.supportedTriggers}
|
||||
supportedTriggers={props.triggers}
|
||||
getTrigger={getTrigger}
|
||||
/>
|
||||
);
|
||||
|
@ -220,7 +212,7 @@ export function createFlyoutManageDrilldowns({
|
|||
// show trigger column in case if there is more then 1 possible trigger in current context
|
||||
const showTriggerColumn =
|
||||
intersection(
|
||||
props.supportedTriggers,
|
||||
props.triggers,
|
||||
actionFactories
|
||||
.map((factory) => factory.supportedTriggers())
|
||||
.reduce((res, next) => res.concat(next), [])
|
||||
|
@ -250,108 +242,3 @@ export function createFlyoutManageDrilldowns({
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
function useCompatibleActionFactoriesForCurrentContext<
|
||||
Context extends BaseActionFactoryContext = BaseActionFactoryContext
|
||||
>(actionFactories: ActionFactory[], context: Context) {
|
||||
const [compatibleActionFactories, setCompatibleActionFactories] = useState<ActionFactory[]>();
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
async function updateCompatibleFactoriesForContext() {
|
||||
const compatibility = await Promise.all(
|
||||
actionFactories.map((factory) => factory.isCompatible(context))
|
||||
);
|
||||
if (canceled) return;
|
||||
|
||||
const compatibleFactories = actionFactories.filter((_, i) => compatibility[i]);
|
||||
const triggerSupportedFactories = compatibleFactories.filter((factory) =>
|
||||
factory.supportedTriggers().some((trigger) => context.triggers.includes(trigger))
|
||||
);
|
||||
setCompatibleActionFactories(triggerSupportedFactories);
|
||||
}
|
||||
updateCompatibleFactoriesForContext();
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [context, actionFactories, context.triggers]);
|
||||
|
||||
return compatibleActionFactories;
|
||||
}
|
||||
|
||||
function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] {
|
||||
const key = `drilldowns:hidWelcomeMessage`;
|
||||
const [hidWelcomeMessage, setHidWelcomeMessage] = useState<boolean>(storage.get(key) ?? false);
|
||||
|
||||
return [
|
||||
!hidWelcomeMessage,
|
||||
() => {
|
||||
if (hidWelcomeMessage) return;
|
||||
setHidWelcomeMessage(true);
|
||||
storage.set(key, true);
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function useDrilldownsStateManager(actionManager: DynamicActionManager, toastService: ToastsStart) {
|
||||
const { events: drilldowns } = useContainerState(actionManager.state);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isMounted = useMountedState();
|
||||
|
||||
async function run(op: () => Promise<void>) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await op();
|
||||
} catch (e) {
|
||||
toastService.addError(e, {
|
||||
title: toastDrilldownsCRUDError,
|
||||
});
|
||||
if (!isMounted) return;
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function createDrilldown(action: SerializedAction, selectedTriggers: TriggerId[]) {
|
||||
await run(async () => {
|
||||
await actionManager.createEvent(action, selectedTriggers);
|
||||
toastService.addSuccess({
|
||||
title: toastDrilldownCreated.title(action.name),
|
||||
text: toastDrilldownCreated.text,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function editDrilldown(
|
||||
drilldownId: string,
|
||||
action: SerializedAction,
|
||||
selectedTriggers: TriggerId[]
|
||||
) {
|
||||
await run(async () => {
|
||||
await actionManager.updateEvent(drilldownId, action, selectedTriggers);
|
||||
toastService.addSuccess({
|
||||
title: toastDrilldownEdited.title(action.name),
|
||||
text: toastDrilldownEdited.text,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteDrilldown(drilldownIds: string | string[]) {
|
||||
await run(async () => {
|
||||
drilldownIds = Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds];
|
||||
await actionManager.deleteEvents(drilldownIds);
|
||||
toastService.addSuccess(
|
||||
drilldownIds.length === 1
|
||||
? {
|
||||
title: toastDrilldownDeleted.title,
|
||||
text: toastDrilldownDeleted.text,
|
||||
}
|
||||
: {
|
||||
title: toastDrilldownsDeleted.title(drilldownIds.length),
|
||||
text: toastDrilldownsDeleted.text,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown };
|
||||
}
|
||||
|
|
|
@ -6,87 +6,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const toastDrilldownCreated = {
|
||||
title: (drilldownName: string) =>
|
||||
i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedTitle',
|
||||
{
|
||||
defaultMessage: 'Drilldown "{drilldownName}" created',
|
||||
values: {
|
||||
drilldownName,
|
||||
},
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedText',
|
||||
{
|
||||
// TODO: remove `Save your dashboard before testing.` part
|
||||
// when drilldowns are used not only in dashboard
|
||||
// or after https://github.com/elastic/kibana/issues/65179 implemented
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownEdited = {
|
||||
title: (drilldownName: string) =>
|
||||
i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedTitle',
|
||||
{
|
||||
defaultMessage: 'Drilldown "{drilldownName}" updated',
|
||||
values: {
|
||||
drilldownName,
|
||||
},
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedText',
|
||||
{
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownDeleted = {
|
||||
title: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedTitle',
|
||||
{
|
||||
defaultMessage: 'Drilldown deleted',
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedText',
|
||||
{
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownsDeleted = {
|
||||
title: (n: number) =>
|
||||
i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedTitle',
|
||||
{
|
||||
defaultMessage: '{n} drilldowns deleted',
|
||||
values: { n },
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedText',
|
||||
{
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownsCRUDError = i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsCRUDErrorTitle',
|
||||
{
|
||||
defaultMessage: 'Error saving drilldown',
|
||||
description: 'Title for generic error toast when persisting drilldown updates failed',
|
||||
}
|
||||
);
|
||||
|
||||
export const insufficientLicenseLevel = i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.insufficientLicenseLevelError',
|
||||
{
|
||||
|
|
|
@ -230,7 +230,7 @@ export function FlyoutDrilldownWizard<
|
|||
actionFactories={drilldownActionFactories}
|
||||
actionFactoryContext={actionFactoryContext}
|
||||
onSelectedTriggersChange={setSelectedTriggers}
|
||||
supportedTriggers={supportedTriggers}
|
||||
triggers={supportedTriggers}
|
||||
getTriggerInfo={getTrigger}
|
||||
triggerPickerDocsLink={triggerPickerDocsLink}
|
||||
/>
|
||||
|
|
|
@ -10,11 +10,7 @@ import { FormDrilldownWizard } from './index';
|
|||
import { Trigger, TriggerId } from '../../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
const otherProps = {
|
||||
supportedTriggers: [
|
||||
'VALUE_CLICK_TRIGGER',
|
||||
'SELECT_RANGE_TRIGGER',
|
||||
'FILTER_TRIGGER',
|
||||
] as TriggerId[],
|
||||
triggers: ['VALUE_CLICK_TRIGGER', 'SELECT_RANGE_TRIGGER', 'FILTER_TRIGGER'] as TriggerId[],
|
||||
getTriggerInfo: (id: TriggerId) => ({ id } as Trigger),
|
||||
onSelectedTriggersChange: () => {},
|
||||
actionFactoryContext: { triggers: [] as TriggerId[] },
|
||||
|
|
|
@ -13,11 +13,7 @@ import { Trigger, TriggerId } from '../../../../../../../src/plugins/ui_actions/
|
|||
|
||||
const otherProps = {
|
||||
actionFactoryContext: { triggers: [] as TriggerId[] },
|
||||
supportedTriggers: [
|
||||
'VALUE_CLICK_TRIGGER',
|
||||
'SELECT_RANGE_TRIGGER',
|
||||
'FILTER_TRIGGER',
|
||||
] as TriggerId[],
|
||||
triggers: ['VALUE_CLICK_TRIGGER', 'SELECT_RANGE_TRIGGER', 'FILTER_TRIGGER'] as TriggerId[],
|
||||
getTriggerInfo: (id: TriggerId) => ({ id } as Trigger),
|
||||
onSelectedTriggersChange: () => {},
|
||||
};
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFieldText, EuiForm, EuiFormRow, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { EuiCode } from '@elastic/eui';
|
||||
import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n';
|
||||
import {
|
||||
ActionFactory,
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
} from '../../../dynamic_actions';
|
||||
import { ActionWizard } from '../../../components/action_wizard';
|
||||
import { Trigger, TriggerId } from '../../../../../../../src/plugins/ui_actions/public';
|
||||
import { txtGetMoreActions } from './i18n';
|
||||
|
||||
const GET_MORE_ACTIONS_LINK = 'https://www.elastic.co/subscriptions';
|
||||
|
||||
|
@ -46,7 +48,7 @@ export interface FormDrilldownWizardProps<
|
|||
/**
|
||||
* List of possible triggers in current context
|
||||
*/
|
||||
supportedTriggers: TriggerId[];
|
||||
triggers: TriggerId[];
|
||||
|
||||
triggerPickerDocsLink?: string;
|
||||
}
|
||||
|
@ -62,9 +64,20 @@ export const FormDrilldownWizard: React.FC<FormDrilldownWizardProps> = ({
|
|||
actionFactoryContext,
|
||||
onSelectedTriggersChange,
|
||||
getTriggerInfo,
|
||||
supportedTriggers,
|
||||
triggers,
|
||||
triggerPickerDocsLink,
|
||||
}) => {
|
||||
if (!triggers || !triggers.length) {
|
||||
// Below callout is not translated, because this message is only for developers.
|
||||
return (
|
||||
<EuiCallOut title="Sorry, there was an error" color="danger" iconType="alert">
|
||||
<p>
|
||||
No triggers provided in <EuiCode>trigger</EuiCode> prop.
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
const nameFragment = (
|
||||
<EuiFormRow label={txtNameOfDrilldown}>
|
||||
<EuiFieldText
|
||||
|
@ -89,10 +102,7 @@ export const FormDrilldownWizard: React.FC<FormDrilldownWizardProps> = ({
|
|||
external
|
||||
data-test-subj={'getMoreActionsLink'}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uiActionsEnhanced.drilldowns.components.FormDrilldownWizard.getMoreActionsLinkLabel"
|
||||
defaultMessage="Get more actions"
|
||||
/>
|
||||
{txtGetMoreActions}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
);
|
||||
|
@ -114,7 +124,7 @@ export const FormDrilldownWizard: React.FC<FormDrilldownWizardProps> = ({
|
|||
context={actionFactoryContext}
|
||||
onSelectedTriggersChange={onSelectedTriggersChange}
|
||||
getTriggerInfo={getTriggerInfo}
|
||||
supportedTriggers={supportedTriggers}
|
||||
triggers={triggers}
|
||||
triggerPickerDocsLink={triggerPickerDocsLink}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -26,3 +26,10 @@ export const txtDrilldownAction = i18n.translate(
|
|||
defaultMessage: 'Action',
|
||||
}
|
||||
);
|
||||
|
||||
export const txtGetMoreActions = i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.FormDrilldownWizard.getMoreActionsLinkLabel',
|
||||
{
|
||||
defaultMessage: 'Get more actions',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const toastDrilldownCreated = {
|
||||
title: (drilldownName: string) =>
|
||||
i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedTitle',
|
||||
{
|
||||
defaultMessage: 'Drilldown "{drilldownName}" created',
|
||||
values: {
|
||||
drilldownName,
|
||||
},
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedText',
|
||||
{
|
||||
// TODO: remove `Save your dashboard before testing.` part
|
||||
// when drilldowns are used not only in dashboard
|
||||
// or after https://github.com/elastic/kibana/issues/65179 implemented
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownEdited = {
|
||||
title: (drilldownName: string) =>
|
||||
i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedTitle',
|
||||
{
|
||||
defaultMessage: 'Drilldown "{drilldownName}" updated',
|
||||
values: {
|
||||
drilldownName,
|
||||
},
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedText',
|
||||
{
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownDeleted = {
|
||||
title: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedTitle',
|
||||
{
|
||||
defaultMessage: 'Drilldown deleted',
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedText',
|
||||
{
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownsDeleted = {
|
||||
title: (n: number) =>
|
||||
i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedTitle',
|
||||
{
|
||||
defaultMessage: '{n} drilldowns deleted',
|
||||
values: { n },
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedText',
|
||||
{
|
||||
defaultMessage: 'Save your dashboard before testing.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const toastDrilldownsCRUDError = i18n.translate(
|
||||
'xpack.uiActionsEnhanced.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsCRUDErrorTitle',
|
||||
{
|
||||
defaultMessage: 'Error saving drilldown',
|
||||
description: 'Title for generic error toast when persisting drilldown updates failed',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ActionFactory, BaseActionFactoryContext } from '../../dynamic_actions';
|
||||
|
||||
export function useCompatibleActionFactoriesForCurrentContext<
|
||||
Context extends BaseActionFactoryContext = BaseActionFactoryContext
|
||||
>(actionFactories: ActionFactory[], context: Context) {
|
||||
const [compatibleActionFactories, setCompatibleActionFactories] = useState<ActionFactory[]>();
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
async function updateCompatibleFactoriesForContext() {
|
||||
const compatibility = await Promise.all(
|
||||
actionFactories.map((factory) => factory.isCompatible(context))
|
||||
);
|
||||
if (canceled) return;
|
||||
|
||||
const compatibleFactories = actionFactories.filter((_, i) => compatibility[i]);
|
||||
const triggerSupportedFactories = compatibleFactories.filter((factory) =>
|
||||
factory.supportedTriggers().some((trigger) => context.triggers.includes(trigger))
|
||||
);
|
||||
setCompatibleActionFactories(triggerSupportedFactories);
|
||||
}
|
||||
updateCompatibleFactoriesForContext();
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [context, actionFactories, context.triggers]);
|
||||
|
||||
return compatibleActionFactories;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ToastsStart } from 'kibana/public';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import { TriggerId } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import { useContainerState } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import {
|
||||
toastDrilldownCreated,
|
||||
toastDrilldownDeleted,
|
||||
toastDrilldownEdited,
|
||||
toastDrilldownsCRUDError,
|
||||
toastDrilldownsDeleted,
|
||||
} from './i18n';
|
||||
import { DynamicActionManager, SerializedAction } from '../../dynamic_actions';
|
||||
|
||||
export function useDrilldownsStateManager(
|
||||
actionManager: DynamicActionManager,
|
||||
toastService: ToastsStart
|
||||
) {
|
||||
const { events: drilldowns } = useContainerState(actionManager.state);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isMounted = useMountedState();
|
||||
|
||||
async function run(op: () => Promise<void>) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await op();
|
||||
} catch (e) {
|
||||
toastService.addError(e, {
|
||||
title: toastDrilldownsCRUDError,
|
||||
});
|
||||
if (!isMounted) return;
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function createDrilldown(action: SerializedAction, selectedTriggers: TriggerId[]) {
|
||||
await run(async () => {
|
||||
await actionManager.createEvent(action, selectedTriggers);
|
||||
toastService.addSuccess({
|
||||
title: toastDrilldownCreated.title(action.name),
|
||||
text: toastDrilldownCreated.text,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function editDrilldown(
|
||||
drilldownId: string,
|
||||
action: SerializedAction,
|
||||
selectedTriggers: TriggerId[]
|
||||
) {
|
||||
await run(async () => {
|
||||
await actionManager.updateEvent(drilldownId, action, selectedTriggers);
|
||||
toastService.addSuccess({
|
||||
title: toastDrilldownEdited.title(action.name),
|
||||
text: toastDrilldownEdited.text,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteDrilldown(drilldownIds: string | string[]) {
|
||||
await run(async () => {
|
||||
drilldownIds = Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds];
|
||||
await actionManager.deleteEvents(drilldownIds);
|
||||
toastService.addSuccess(
|
||||
drilldownIds.length === 1
|
||||
? {
|
||||
title: toastDrilldownDeleted.title,
|
||||
text: toastDrilldownDeleted.text,
|
||||
}
|
||||
: {
|
||||
title: toastDrilldownsDeleted.title(drilldownIds.length),
|
||||
text: toastDrilldownsDeleted.text,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown };
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] {
|
||||
const key = `drilldowns:hidWelcomeMessage`;
|
||||
const [hideWelcomeMessage, setHideWelcomeMessage] = useState<boolean>(storage.get(key) ?? false);
|
||||
|
||||
return [
|
||||
!hideWelcomeMessage,
|
||||
() => {
|
||||
if (hideWelcomeMessage) return;
|
||||
setHideWelcomeMessage(true);
|
||||
storage.set(key, true);
|
||||
},
|
||||
];
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue