From 2e2fc1baba6e3d1e791a9956c6acf06000974a0f Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Sat, 28 Jul 2018 10:41:57 -0400 Subject: [PATCH] Add pluggable panel action tests (#20163) * Add pluggable panel action tests * address code review comments * update inspector snapshot * remove temp declared ts module now that eui has EuiFlyout typings * address code comments --- scripts/functional_tests.js | 1 + src/ui/public/flyout/flyout_session.tsx | 115 +++++ src/ui/public/flyout/index.ts | 20 + src/ui/public/inspector/inspector.tsx | 80 +-- .../inspector_panel.test.js.snap | 472 +++++++----------- .../public/inspector/ui/inspector_panel.d.ts | 1 - src/ui/public/inspector/ui/inspector_panel.js | 11 +- tasks/config/run.js | 11 + tasks/test.js | 46 +- test/panel_actions/config.js | 50 ++ test/panel_actions/index.js | 50 ++ test/panel_actions/panel_actions.js | 54 ++ .../sample_panel_action/index.js | 33 ++ .../sample_panel_action/package.json | 8 + .../public/sample_panel_action.js | 55 ++ 15 files changed, 609 insertions(+), 398 deletions(-) create mode 100644 src/ui/public/flyout/flyout_session.tsx create mode 100644 src/ui/public/flyout/index.ts create mode 100644 test/panel_actions/config.js create mode 100644 test/panel_actions/index.js create mode 100644 test/panel_actions/panel_actions.js create mode 100644 test/panel_actions/sample_panel_action/index.js create mode 100644 test/panel_actions/sample_panel_action/package.json create mode 100644 test/panel_actions/sample_panel_action/public/sample_panel_action.js diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index 75e4eab07ac06..4d4f255aa02ad 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -21,4 +21,5 @@ require('../src/setup_node_env'); require('../packages/kbn-test').runTestsCli([ require.resolve('../test/functional/config.js'), require.resolve('../test/api_integration/config.js'), + require.resolve('../test/panel_actions/config.js'), ]); diff --git a/src/ui/public/flyout/flyout_session.tsx b/src/ui/public/flyout/flyout_session.tsx new file mode 100644 index 0000000000000..185c7378fa86a --- /dev/null +++ b/src/ui/public/flyout/flyout_session.tsx @@ -0,0 +1,115 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { EuiFlyout } from '@elastic/eui'; +import { EventEmitter } from 'events'; +import ReactDOM from 'react-dom'; + +let activeSession: FlyoutSession | null = null; + +const CONTAINER_ID = 'flyout-container'; + +function getOrCreateContainerElement() { + let container = document.getElementById(CONTAINER_ID); + if (!container) { + container = document.createElement('div'); + container.id = CONTAINER_ID; + document.body.appendChild(container); + } + return container; +} + +/** + * A FlyoutSession describes the session of one opened flyout panel. It offers + * methods to close the flyout panel again. If you open a flyout panel you should make + * sure you call {@link FlyoutSession#close} when it should be closed. + * Since a flyout could also be closed without calling this method (e.g. because + * the user closes it), you must listen to the "closed" event on this instance. + * It will be emitted whenever the flyout will be closed and you should throw + * away your reference to this instance whenever you receive that event. + * @extends EventEmitter + */ +class FlyoutSession extends EventEmitter { + /** + * Binds the current flyout session to an Angular scope, meaning this flyout + * session will be closed as soon as the Angular scope gets destroyed. + * @param {object} scope - An angular scope object to bind to. + */ + public bindToAngularScope(scope: ng.IScope): void { + const removeWatch = scope.$on('$destroy', () => this.close()); + this.on('closed', () => removeWatch()); + } + + /** + * Closes the opened flyout as long as it's still the open one. + * If this is not the active session anymore, this method won't do anything. + * If this session was still active and a flyout was closed, the 'closed' + * event will be emitted on this FlyoutSession instance. + */ + public close(): void { + if (activeSession === this) { + const container = document.getElementById(CONTAINER_ID); + if (container) { + ReactDOM.unmountComponentAtNode(container); + this.emit('closed'); + } + } + } +} + +/** + * Opens a flyout panel with the given component inside. You can use + * {@link FlyoutSession#close} on the return value to close the flyout. + * + * @param flyoutChildren - Mounts the children inside a fly out panel + * @return {FlyoutSession} The session instance for the opened flyout panel. + */ +export function openFlyout( + flyoutChildren: React.ReactNode, + flyoutProps: { + onClose?: () => void; + 'data-test-subj'?: string; + } = {} +): FlyoutSession { + // If there is an active inspector session close it before opening a new one. + if (activeSession) { + activeSession.close(); + } + const container = getOrCreateContainerElement(); + const session = (activeSession = new FlyoutSession()); + const onClose = () => { + if (flyoutProps.onClose) { + flyoutProps.onClose(); + } + session.close(); + }; + + ReactDOM.render( + + {flyoutChildren} + , + container + ); + + return session; +} + +export { FlyoutSession }; diff --git a/src/ui/public/flyout/index.ts b/src/ui/public/flyout/index.ts new file mode 100644 index 0000000000000..2c0f11bcc72ba --- /dev/null +++ b/src/ui/public/flyout/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export * from './flyout_session'; diff --git a/src/ui/public/inspector/inspector.tsx b/src/ui/public/inspector/inspector.tsx index d463cba47468c..7d19674945a6a 100644 --- a/src/ui/public/inspector/inspector.tsx +++ b/src/ui/public/inspector/inspector.tsx @@ -16,67 +16,13 @@ * specific language governing permissions and limitations * under the License. */ - -import { EventEmitter } from 'events'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { FlyoutSession, openFlyout } from 'ui/flyout'; import { Adapters } from './types'; import { InspectorPanel } from './ui/inspector_panel'; import { viewRegistry } from './view_registry'; -let activeSession: InspectorSession | null = null; - -const CONTAINER_ID = 'inspector-container'; - -function getOrCreateContainerElement() { - let container = document.getElementById(CONTAINER_ID); - if (!container) { - container = document.createElement('div'); - container.id = CONTAINER_ID; - document.body.appendChild(container); - } - return container; -} - -/** - * An InspectorSession describes the session of one opened inspector. It offers - * methods to close the inspector again. If you open an inspector you should make - * sure you call {@link InspectorSession#close} when it should be closed. - * Since an inspector could also be closed without calling this method (e.g. because - * the user closes it), you must listen to the "closed" event on this instance. - * It will be emitted whenever the inspector will be closed and you should throw - * away your reference to this instance whenever you receive that event. - * @extends EventEmitter - */ -class InspectorSession extends EventEmitter { - /** - * Binds the current inspector session to an Angular scope, meaning this inspector - * session will be closed as soon as the Angular scope gets destroyed. - * @param {object} scope - And angular scope object to bind to. - */ - public bindToAngularScope(scope: ng.IScope): void { - const removeWatch = scope.$on('$destroy', () => this.close()); - this.on('closed', () => removeWatch()); - } - - /** - * Closes the opened inspector as long as it's stil the open one. - * If this is not the active session anymore, this method won't do anything. - * If this session was still active and an inspector was closed, the 'closed' - * event will be emitted on this InspectorSession instance. - */ - public close(): void { - if (activeSession === this) { - const container = document.getElementById(CONTAINER_ID); - if (container) { - ReactDOM.unmountComponentAtNode(container); - this.emit('closed'); - } - } - } -} - /** * Checks if a inspector panel could be shown based on the passed adapters. * @@ -98,6 +44,8 @@ interface InspectorOptions { title?: string; } +export type InspectorSession = FlyoutSession; + /** * Opens the inspector panel for the given adapters and close any previously opened * inspector panel. The previously panel will be closed also if no new panel will be @@ -110,11 +58,6 @@ interface InspectorOptions { * @return {InspectorSession} The session instance for the opened inspector. */ function open(adapters: Adapters, options: InspectorOptions = {}): InspectorSession { - // If there is an active inspector session close it before opening a new one. - if (activeSession) { - activeSession.close(); - } - const views = viewRegistry.getVisible(adapters); // Don't open inspector if there are no views available for the passed adapters @@ -124,20 +67,9 @@ function open(adapters: Adapters, options: InspectorOptions = {}): InspectorSess if an inspector can be shown.`); } - const container = getOrCreateContainerElement(); - const session = (activeSession = new InspectorSession()); - - ReactDOM.render( - session.close()} - title={options.title} - />, - container - ); - - return session; + return openFlyout(, { + 'data-test-subj': 'inspectorPanel', + }); } const Inspector = { diff --git a/src/ui/public/inspector/ui/__snapshots__/inspector_panel.test.js.snap b/src/ui/public/inspector/ui/__snapshots__/inspector_panel.test.js.snap index 3ec781c8e7b1d..8fff7e5caf3b8 100644 --- a/src/ui/public/inspector/ui/__snapshots__/inspector_panel.test.js.snap +++ b/src/ui/public/inspector/ui/__snapshots__/inspector_panel.test.js.snap @@ -34,313 +34,213 @@ exports[`InspectorPanel should render as expected 1`] = ` ] } > - - - + -
-
+ - - - - + +
+ + +
-
- + View: + View 1 + + } + closePopover={[Function]} + id="inspectorViewChooser" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" > -
-
- -

- Inspector -

-
-
-
- -
- - - View: - View 1 - - } - closePopover={[Function]} - id="inspectorViewChooser" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" + - -
-
- - - + + + + + + + + + View: + View 1 + + + +
- -
- -
- - -

- View 1 -

-
- + + + + + + -
-
-
+ + + + +

+ View 1 +

+
`; diff --git a/src/ui/public/inspector/ui/inspector_panel.d.ts b/src/ui/public/inspector/ui/inspector_panel.d.ts index 14aea5d7229d1..154b1a58f5732 100644 --- a/src/ui/public/inspector/ui/inspector_panel.d.ts +++ b/src/ui/public/inspector/ui/inspector_panel.d.ts @@ -22,7 +22,6 @@ import { Adapters, InspectorViewDescription } from '../types'; interface InspectorPanelProps { adapters: Adapters; - onClose: () => void; title?: string; views: InspectorViewDescription[]; } diff --git a/src/ui/public/inspector/ui/inspector_panel.js b/src/ui/public/inspector/ui/inspector_panel.js index 2c0c3dd9ae29e..7ec34d732387e 100644 --- a/src/ui/public/inspector/ui/inspector_panel.js +++ b/src/ui/public/inspector/ui/inspector_panel.js @@ -22,7 +22,6 @@ import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, - EuiFlyout, EuiFlyoutHeader, EuiTitle, } from '@elastic/eui'; @@ -79,14 +78,11 @@ class InspectorPanel extends Component { } render() { - const { views, onClose, title } = this.props; + const { views, title } = this.props; const { selectedView } = this.state; return ( - + { this.renderSelectedPanel() } - + ); } } @@ -125,7 +121,6 @@ InspectorPanel.propTypes = { ); } }, - onClose: PropTypes.func.isRequired, title: PropTypes.string, }; diff --git a/tasks/config/run.js b/tasks/config/run.js index 1aacdca1c376c..fb6c4f8644782 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -156,6 +156,17 @@ module.exports = function (grunt) { ], }, + panelActionTests: { + cmd: process.execPath, + args: [ + 'scripts/functional_tests', + '--config', 'test/panel_actions/config.js', + '--esFrom', 'source', + '--bail', + '--debug', + ], + }, + functionalTests: { cmd: process.execPath, args: [ diff --git a/tasks/test.js b/tasks/test.js index 77d73237c9c51..87d4fc26a4715 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -31,16 +31,9 @@ module.exports = function (grunt) { } ); - grunt.registerTask('test:server', [ - 'checkPlugins', - 'run:mocha', - ]); + grunt.registerTask('test:server', ['checkPlugins', 'run:mocha']); - grunt.registerTask('test:browser', [ - 'checkPlugins', - 'run:browserTestServer', - 'karma:unit', - ]); + grunt.registerTask('test:browser', ['checkPlugins', 'run:browserTestServer', 'karma:unit']); grunt.registerTask('test:browser-ci', () => { const ciShardTasks = keys(grunt.config.get('karma')) @@ -49,13 +42,10 @@ module.exports = function (grunt) { grunt.log.ok(`Running UI tests in ${ciShardTasks.length} shards`); - grunt.task.run([ - 'run:browserTestServer', - ...ciShardTasks - ]); + grunt.task.run(['run:browserTestServer', ...ciShardTasks]); }); - grunt.registerTask('test:coverage', [ 'run:testCoverageServer', 'karma:coverage' ]); + grunt.registerTask('test:coverage', ['run:testCoverageServer', 'karma:coverage']); grunt.registerTask('test:quick', [ 'checkPlugins', @@ -65,26 +55,24 @@ module.exports = function (grunt) { 'test:jest_integration', 'test:projects', 'test:browser', - 'run:apiIntegrationTests' + 'run:apiIntegrationTests', ]); - grunt.registerTask('test:dev', [ - 'checkPlugins', - 'run:devBrowserTestServer', - 'karma:dev' - ]); + grunt.registerTask('test:dev', ['checkPlugins', 'run:devBrowserTestServer', 'karma:dev']); grunt.registerTask('test', subTask => { if (subTask) grunt.fail.fatal(`invalid task "test:${subTask}"`); - grunt.task.run(_.compact([ - !grunt.option('quick') && 'run:eslint', - !grunt.option('quick') && 'run:tslint', - 'run:checkFileCasing', - 'licenses', - 'test:quick', - 'verifyTranslations', - ])); + grunt.task.run( + _.compact([ + !grunt.option('quick') && 'run:eslint', + !grunt.option('quick') && 'run:tslint', + 'run:checkFileCasing', + 'licenses', + 'test:quick', + 'verifyTranslations', + ]) + ); }); grunt.registerTask('quick-test', ['test:quick']); // historical alias @@ -98,7 +86,7 @@ module.exports = function (grunt) { const serverCmd = { cmd: 'yarn', args: ['kbn', 'run', 'test', '--exclude', 'kibana', '--oss', '--skip-kibana-extra'], - opts: { stdio: 'inherit' } + opts: { stdio: 'inherit' }, }; return new Promise((resolve, reject) => { diff --git a/test/panel_actions/config.js b/test/panel_actions/config.js new file mode 100644 index 0000000000000..8493c56ae8687 --- /dev/null +++ b/test/panel_actions/config.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import path from 'path'; + +export default async function ({ readConfigFile }) { + const functionalConfig = await readConfigFile(require.resolve('../functional/config')); + + return { + testFiles: [ + require.resolve('./index'), + ], + services: functionalConfig.get('services'), + pageObjects: functionalConfig.get('pageObjects'), + servers: functionalConfig.get('servers'), + env: functionalConfig.get('env'), + esTestCluster: functionalConfig.get('esTestCluster'), + apps: functionalConfig.get('apps'), + esArchiver: { + directory: path.resolve(__dirname, '../es_archives') + }, + screenshots: functionalConfig.get('screenshots'), + junit: { + reportName: 'Panel Actions Functional Tests', + }, + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + `--plugin-path=${path.resolve(__dirname, './sample_panel_action')}`, + ], + }, + }; +} diff --git a/test/panel_actions/index.js b/test/panel_actions/index.js new file mode 100644 index 0000000000000..48f743371f326 --- /dev/null +++ b/test/panel_actions/index.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import path from 'path'; + +export const KIBANA_ARCHIVE_PATH = path.resolve(__dirname, '../functional/fixtures/es_archiver/dashboard/current/kibana'); +export const DATA_ARCHIVE_PATH = path.resolve(__dirname, '../functional/fixtures/es_archiver/dashboard/current/data'); + + +export default function ({ getService, getPageObjects, loadTestFile }) { + const remote = getService('remote'); + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['dashboard']); + + describe('pluggable panel actions', function () { + before(async () => { + await remote.setWindowSize(1300, 900); + await PageObjects.dashboard.initTests({ + kibanaIndex: KIBANA_ARCHIVE_PATH, + dataIndex: DATA_ARCHIVE_PATH, + defaultIndex: 'logstash-*', + }); + await PageObjects.dashboard.preserveCrossAppState(); + }); + + after(async function () { + await PageObjects.dashboard.clearSavedObjectsFromAppLinks(); + await esArchiver.unload(KIBANA_ARCHIVE_PATH); + await esArchiver.unload(DATA_ARCHIVE_PATH); + }); + + loadTestFile(require.resolve('./panel_actions')); + }); +} diff --git a/test/panel_actions/panel_actions.js b/test/panel_actions/panel_actions.js new file mode 100644 index 0000000000000..29605638b025a --- /dev/null +++ b/test/panel_actions/panel_actions.js @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from 'expect.js'; + +export default function ({ getService, getPageObjects }) { + const dashboardPanelActions = getService('dashboardPanelActions'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['dashboard']); + + describe('Panel Actions', () => { + before(async () => { + await PageObjects.dashboard.loadSavedDashboard('few panels'); + }); + + it('Sample action appears in context menu in view mode', async () => { + await dashboardPanelActions.openContextMenu(); + const newPanelActionExists = await testSubjects.exists( + 'dashboardPanelAction-samplePanelAction' + ); + expect(newPanelActionExists).to.be(true); + }); + + it('Clicking sample action shows a flyout', async () => { + await dashboardPanelActions.openContextMenu(); + await testSubjects.click('dashboardPanelAction-samplePanelAction'); + const flyoutExists = await testSubjects.exists('samplePanelActionFlyout'); + expect(flyoutExists).to.be(true); + }); + + it('flyout shows the correct contents', async () => { + const titleExists = await testSubjects.exists('samplePanelActionTitle'); + expect(titleExists).to.be(true); + const bodyExists = await testSubjects.exists('samplePanelActionBody'); + expect(bodyExists).to.be(true); + }); + }); +} diff --git a/test/panel_actions/sample_panel_action/index.js b/test/panel_actions/sample_panel_action/index.js new file mode 100644 index 0000000000000..3702b17846fe6 --- /dev/null +++ b/test/panel_actions/sample_panel_action/index.js @@ -0,0 +1,33 @@ +/* + * 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. + */ + +function samplePanelAction(kibana) { + return new kibana.Plugin({ + uiExports: { + dashboardPanelActions: ['plugins/sample_panel_action/sample_panel_action'], + }, + }); +} + +module.exports = function (kibana) { + return [ + samplePanelAction(kibana), + ]; +}; + diff --git a/test/panel_actions/sample_panel_action/package.json b/test/panel_actions/sample_panel_action/package.json new file mode 100644 index 0000000000000..f1603503a439b --- /dev/null +++ b/test/panel_actions/sample_panel_action/package.json @@ -0,0 +1,8 @@ +{ + "name": "sample_panel_action", + "version": "7.0.0-alpha1", + "dependencies": { + "@elastic/eui": "0.0.55", + "react": "^16.4.1" + } +} diff --git a/test/panel_actions/sample_panel_action/public/sample_panel_action.js b/test/panel_actions/sample_panel_action/public/sample_panel_action.js new file mode 100644 index 0000000000000..3f5cbf8f18f43 --- /dev/null +++ b/test/panel_actions/sample_panel_action/public/sample_panel_action.js @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { openFlyout } from '../../../../src/ui/public/flyout'; + +import { + DashboardPanelAction, + DashboardPanelActionsRegistryProvider, +} from '../../../../src/ui/public/dashboard_panel_actions'; + +class SamplePanelAction extends DashboardPanelAction { + constructor() { + super({ + displayName: 'Sample Panel Action', + id: 'samplePanelAction', + parentPanelId: 'mainMenu', + }); + } + onClick({ embeddable }) { + openFlyout( + + + +

{embeddable.metadata.title}

+
+
+ +

This is a sample action

+
+
, + { + 'data-test-subj': 'samplePanelActionFlyout', + }, + ); + } +} + +DashboardPanelActionsRegistryProvider.register(() => new SamplePanelAction());