mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* 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
This commit is contained in:
parent
77f8cc3b6e
commit
38978a07ae
15 changed files with 611 additions and 400 deletions
|
@ -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'),
|
||||
]);
|
||||
|
|
115
src/ui/public/flyout/flyout_session.tsx
Normal file
115
src/ui/public/flyout/flyout_session.tsx
Normal file
|
@ -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(
|
||||
<EuiFlyout {...flyoutProps} onClose={onClose}>
|
||||
{flyoutChildren}
|
||||
</EuiFlyout>,
|
||||
container
|
||||
);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export { FlyoutSession };
|
20
src/ui/public/flyout/index.ts
Normal file
20
src/ui/public/flyout/index.ts
Normal file
|
@ -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';
|
|
@ -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(
|
||||
<InspectorPanel
|
||||
views={views}
|
||||
adapters={adapters}
|
||||
onClose={() => session.close()}
|
||||
title={options.title}
|
||||
/>,
|
||||
container
|
||||
);
|
||||
|
||||
return session;
|
||||
return openFlyout(<InspectorPanel views={views} adapters={adapters} title={options.title} />, {
|
||||
'data-test-subj': 'inspectorPanel',
|
||||
});
|
||||
}
|
||||
|
||||
const Inspector = {
|
||||
|
|
|
@ -34,313 +34,213 @@ exports[`InspectorPanel should render as expected 1`] = `
|
|||
]
|
||||
}
|
||||
>
|
||||
<EuiFlyout
|
||||
data-test-subj="inspectorPanel"
|
||||
hideCloseButton={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
<EuiFlyoutHeader
|
||||
hasBorder={true}
|
||||
>
|
||||
<span>
|
||||
<FocusTrap
|
||||
_createFocusTrap={[Function]}
|
||||
active={true}
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"clickOutsideDeactivates": true,
|
||||
"fallbackFocus": [Function],
|
||||
}
|
||||
}
|
||||
paused={false}
|
||||
tag="div"
|
||||
<div
|
||||
className="euiFlyoutHeader euiFlyoutHeader--hasBorder"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
justifyContent="spaceBetween"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="euiFlyout euiFlyout--medium"
|
||||
data-test-subj="inspectorPanel"
|
||||
onKeyDown={[Function]}
|
||||
role="dialog"
|
||||
tabIndex={0}
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Closes this dialog"
|
||||
className="euiFlyout__closeButton"
|
||||
color="text"
|
||||
data-test-subj="euiFlyoutCloseButton"
|
||||
iconType="cross"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
aria-label="Closes this dialog"
|
||||
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
|
||||
data-test-subj="euiFlyoutCloseButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
<EuiTitle
|
||||
size="s"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
size="m"
|
||||
type="cross"
|
||||
<h1
|
||||
className="euiTitle euiTitle--small"
|
||||
>
|
||||
<cross
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={
|
||||
Object {
|
||||
"fill": undefined,
|
||||
}
|
||||
}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={
|
||||
Object {
|
||||
"fill": undefined,
|
||||
}
|
||||
}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
Inspector
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<InspectorViewChooser
|
||||
onViewSelected={[Function]}
|
||||
selectedView={
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 200,
|
||||
"title": "View 1",
|
||||
}
|
||||
}
|
||||
views={
|
||||
Array [
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 200,
|
||||
"title": "View 1",
|
||||
},
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 100,
|
||||
"shouldShow": [Function],
|
||||
"title": "Foo View",
|
||||
},
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 200,
|
||||
"shouldShow": [Function],
|
||||
"title": "Never",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiPopover
|
||||
anchorPosition="downRight"
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
data-test-subj="inspectorViewChooser"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M7.293 8l-4.147 4.146a.5.5 0 0 0 .708.708L8 8.707l4.146 4.147a.5.5 0 0 0 .708-.708L8.707 8l4.147-4.146a.5.5 0 0 0-.708-.708L8 7.293 3.854 3.146a.5.5 0 1 0-.708.708L7.293 8z"
|
||||
id="cross-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fillRule="nonzero"
|
||||
xlinkHref="#cross-a"
|
||||
/>
|
||||
</svg>
|
||||
</cross>
|
||||
</EuiIcon>
|
||||
</button>
|
||||
</EuiButtonIcon>
|
||||
<EuiFlyoutHeader
|
||||
hasBorder={true}
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutHeader euiFlyoutHeader--hasBorder"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
justifyContent="spaceBetween"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
View:
|
||||
View 1
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
closePopover={[Function]}
|
||||
id="inspectorViewChooser"
|
||||
isOpen={false}
|
||||
ownFocus={true}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={[Function]}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownRight"
|
||||
id="inspectorViewChooser"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<EuiTitle
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
data-test-subj="inspectorViewChooser"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
>
|
||||
<h1
|
||||
className="euiTitle euiTitle--small"
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small euiButtonEmpty--iconRight"
|
||||
data-test-subj="inspectorViewChooser"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Inspector
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<InspectorViewChooser
|
||||
onViewSelected={[Function]}
|
||||
selectedView={
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 200,
|
||||
"title": "View 1",
|
||||
}
|
||||
}
|
||||
views={
|
||||
Array [
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 200,
|
||||
"title": "View 1",
|
||||
},
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 100,
|
||||
"shouldShow": [Function],
|
||||
"title": "Foo View",
|
||||
},
|
||||
Object {
|
||||
"component": [Function],
|
||||
"order": 200,
|
||||
"shouldShow": [Function],
|
||||
"title": "Never",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiPopover
|
||||
anchorPosition="downRight"
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
data-test-subj="inspectorViewChooser"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiButtonEmpty__icon"
|
||||
size="m"
|
||||
type="arrowDown"
|
||||
>
|
||||
<arrowDown
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonEmpty__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={
|
||||
Object {
|
||||
"fill": undefined,
|
||||
}
|
||||
}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonEmpty__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={
|
||||
Object {
|
||||
"fill": undefined,
|
||||
}
|
||||
}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M13.069 5.157L8.384 9.768a.546.546 0 0 1-.768 0L2.93 5.158a.552.552 0 0 0-.771 0 .53.53 0 0 0 0 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 0 0 0-.76.552.552 0 0 0-.771 0z"
|
||||
id="arrow_down-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fillRule="nonzero"
|
||||
xlinkHref="#arrow_down-a"
|
||||
/>
|
||||
</svg>
|
||||
</arrowDown>
|
||||
</EuiIcon>
|
||||
<span>
|
||||
View:
|
||||
View 1
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
closePopover={[Function]}
|
||||
id="inspectorViewChooser"
|
||||
isOpen={false}
|
||||
ownFocus={true}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownRight"
|
||||
id="inspectorViewChooser"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
data-test-subj="inspectorViewChooser"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small euiButtonEmpty--iconRight"
|
||||
data-test-subj="inspectorViewChooser"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiButtonEmpty__icon"
|
||||
size="m"
|
||||
type="arrowDown"
|
||||
>
|
||||
<arrowDown
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonEmpty__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={
|
||||
Object {
|
||||
"fill": undefined,
|
||||
}
|
||||
}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiButtonEmpty__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
style={
|
||||
Object {
|
||||
"fill": undefined,
|
||||
}
|
||||
}
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M13.069 5.157L8.384 9.768a.546.546 0 0 1-.768 0L2.93 5.158a.552.552 0 0 0-.771 0 .53.53 0 0 0 0 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 0 0 0-.76.552.552 0 0 0-.771 0z"
|
||||
id="arrow_down-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fillRule="nonzero"
|
||||
xlinkHref="#arrow_down-a"
|
||||
/>
|
||||
</svg>
|
||||
</arrowDown>
|
||||
</EuiIcon>
|
||||
<span>
|
||||
View:
|
||||
View 1
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</div>
|
||||
</EuiOutsideClickDetector>
|
||||
</EuiPopover>
|
||||
</InspectorViewChooser>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlyoutHeader>
|
||||
<component
|
||||
adapters={
|
||||
Object {
|
||||
"bardapter": Object {},
|
||||
"foodapter": Object {
|
||||
"foo": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
title="Inspector"
|
||||
>
|
||||
<h1>
|
||||
View 1
|
||||
</h1>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</EuiOutsideClickDetector>
|
||||
</EuiPopover>
|
||||
</InspectorViewChooser>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
</span>
|
||||
</EuiFlyout>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlyoutHeader>
|
||||
<component
|
||||
adapters={
|
||||
Object {
|
||||
"bardapter": Object {},
|
||||
"foodapter": Object {
|
||||
"foo": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
title="Inspector"
|
||||
>
|
||||
<h1>
|
||||
View 1
|
||||
</h1>
|
||||
</component>
|
||||
</InspectorPanel>
|
||||
`;
|
||||
|
|
|
@ -22,7 +22,6 @@ import { Adapters, InspectorViewDescription } from '../types';
|
|||
|
||||
interface InspectorPanelProps {
|
||||
adapters: Adapters;
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
views: InspectorViewDescription[];
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<EuiFlyout
|
||||
onClose={onClose}
|
||||
data-test-subj="inspectorPanel"
|
||||
>
|
||||
<React.Fragment>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
|
@ -107,7 +103,7 @@ class InspectorPanel extends Component {
|
|||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
{ this.renderSelectedPanel() }
|
||||
</EuiFlyout>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +121,6 @@ InspectorPanel.propTypes = {
|
|||
);
|
||||
}
|
||||
},
|
||||
onClose: PropTypes.func.isRequired,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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) => {
|
||||
|
|
50
test/panel_actions/config.js
Normal file
50
test/panel_actions/config.js
Normal file
|
@ -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')}`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
50
test/panel_actions/index.js
Normal file
50
test/panel_actions/index.js
Normal file
|
@ -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'));
|
||||
});
|
||||
}
|
54
test/panel_actions/panel_actions.js
Normal file
54
test/panel_actions/panel_actions.js
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
33
test/panel_actions/sample_panel_action/index.js
Normal file
33
test/panel_actions/sample_panel_action/index.js
Normal file
|
@ -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),
|
||||
];
|
||||
};
|
||||
|
8
test/panel_actions/sample_panel_action/package.json
Normal file
8
test/panel_actions/sample_panel_action/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "sample_panel_action",
|
||||
"version": "7.0.0-alpha1",
|
||||
"dependencies": {
|
||||
"@elastic/eui": "0.0.55",
|
||||
"react": "^16.4.1"
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
<React.Fragment>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="s" data-test-subj="samplePanelActionTitle">
|
||||
<h1>{embeddable.metadata.title}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<h1 data-test-subj="samplePanelActionBody">This is a sample action</h1>
|
||||
</EuiFlyoutBody>
|
||||
</React.Fragment>,
|
||||
{
|
||||
'data-test-subj': 'samplePanelActionFlyout',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DashboardPanelActionsRegistryProvider.register(() => new SamplePanelAction());
|
Loading…
Add table
Add a link
Reference in a new issue