mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Added copy to dashboard action
This commit is contained in:
parent
ac8f5bc23b
commit
4396e45c4d
24 changed files with 494 additions and 16 deletions
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [OverlayModalOpenOptions](./kibana-plugin-core-public.overlaymodalopenoptions.md) > [maxWidth](./kibana-plugin-core-public.overlaymodalopenoptions.maxwidth.md)
|
||||
|
||||
## OverlayModalOpenOptions.maxWidth property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
maxWidth?: boolean | number | string;
|
||||
```
|
|
@ -18,4 +18,5 @@ export interface OverlayModalOpenOptions
|
|||
| ["data-test-subj"](./kibana-plugin-core-public.overlaymodalopenoptions._data-test-subj_.md) | <code>string</code> | |
|
||||
| [className](./kibana-plugin-core-public.overlaymodalopenoptions.classname.md) | <code>string</code> | |
|
||||
| [closeButtonAriaLabel](./kibana-plugin-core-public.overlaymodalopenoptions.closebuttonarialabel.md) | <code>string</code> | |
|
||||
| [maxWidth](./kibana-plugin-core-public.overlaymodalopenoptions.maxwidth.md) | <code>boolean | number | string</code> | |
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ export interface OverlayModalOpenOptions {
|
|||
className?: string;
|
||||
closeButtonAriaLabel?: string;
|
||||
'data-test-subj'?: string;
|
||||
maxWidth?: boolean | number | string;
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
|
|
|
@ -976,6 +976,8 @@ export interface OverlayModalOpenOptions {
|
|||
className?: string;
|
||||
// (undocumented)
|
||||
closeButtonAriaLabel?: string;
|
||||
// (undocumented)
|
||||
maxWidth?: boolean | number | string;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
"savedObjects",
|
||||
"share",
|
||||
"uiActions",
|
||||
"urlForwarding"
|
||||
"urlForwarding",
|
||||
"presentationUtil"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"home",
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { OverlayStart } from '../../../../../core/public';
|
||||
import { dashboardCopyToDashboardAction } from '../../dashboard_strings';
|
||||
import { EmbeddableStateTransfer, IEmbeddable } from '../../services/embeddable';
|
||||
import { toMountPoint } from '../../services/kibana_react';
|
||||
import { PresentationUtilPluginStart } from '../../services/presentation_util';
|
||||
import { Action, IncompatibleActionError } from '../../services/ui_actions';
|
||||
import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable';
|
||||
import { CopyToDashboardModal } from './copy_to_dashboard_modal';
|
||||
|
||||
export const ACTION_COPY_TO_DASHBOARD = 'copyToDashboard';
|
||||
|
||||
export interface CopyToDashboardActionContext {
|
||||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
export interface DashboardCopyToCapabilities {
|
||||
canCreateNew: boolean;
|
||||
canEditExisting: boolean;
|
||||
}
|
||||
|
||||
function isDashboard(embeddable: IEmbeddable): embeddable is DashboardContainer {
|
||||
return embeddable.type === DASHBOARD_CONTAINER_TYPE;
|
||||
}
|
||||
|
||||
export class CopyToDashboardAction implements Action<CopyToDashboardActionContext> {
|
||||
public readonly type = ACTION_COPY_TO_DASHBOARD;
|
||||
public readonly id = ACTION_COPY_TO_DASHBOARD;
|
||||
public order = 1;
|
||||
|
||||
constructor(
|
||||
private overlays: OverlayStart,
|
||||
private stateTransfer: EmbeddableStateTransfer,
|
||||
private capabilities: DashboardCopyToCapabilities,
|
||||
private PresentationUtilContext: PresentationUtilPluginStart['ContextProvider']
|
||||
) {}
|
||||
|
||||
public getDisplayName({ embeddable }: CopyToDashboardActionContext) {
|
||||
if (!embeddable.parent || !isDashboard(embeddable.parent)) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
return dashboardCopyToDashboardAction.getDisplayName();
|
||||
}
|
||||
|
||||
public getIconType({ embeddable }: CopyToDashboardActionContext) {
|
||||
if (!embeddable.parent || !isDashboard(embeddable.parent)) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
return 'exit';
|
||||
}
|
||||
|
||||
public async isCompatible({ embeddable }: CopyToDashboardActionContext) {
|
||||
return Boolean(
|
||||
embeddable.parent &&
|
||||
isDashboard(embeddable.parent) &&
|
||||
(this.capabilities.canCreateNew || this.capabilities.canEditExisting)
|
||||
);
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: CopyToDashboardActionContext) {
|
||||
if (!embeddable.parent || !isDashboard(embeddable.parent)) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
const session = this.overlays.openModal(
|
||||
toMountPoint(
|
||||
<CopyToDashboardModal
|
||||
PresentationUtilContext={this.PresentationUtilContext}
|
||||
closeModal={() => session.close()}
|
||||
stateTransfer={this.stateTransfer}
|
||||
capabilities={this.capabilities}
|
||||
dashboardId={(embeddable.parent as DashboardContainer).getInput().id}
|
||||
embeddable={embeddable}
|
||||
/>
|
||||
),
|
||||
{
|
||||
maxWidth: 400,
|
||||
'data-test-subj': 'copyToDashboardPanel',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFormRow,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiPanel,
|
||||
EuiRadio,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { DashboardCopyToCapabilities } from './copy_to_dashboard_action';
|
||||
import { DashboardPicker } from '../../services/presentation_util';
|
||||
import { dashboardCopyToDashboardAction } from '../../dashboard_strings';
|
||||
import { EmbeddableStateTransfer, IEmbeddable } from '../../services/embeddable';
|
||||
import { createDashboardEditUrl, DashboardConstants } from '../..';
|
||||
|
||||
interface CopyToDashboardModalProps {
|
||||
capabilities: DashboardCopyToCapabilities;
|
||||
stateTransfer: EmbeddableStateTransfer;
|
||||
PresentationUtilContext: React.FC;
|
||||
embeddable: IEmbeddable;
|
||||
dashboardId?: string;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
export function CopyToDashboardModal({
|
||||
PresentationUtilContext,
|
||||
stateTransfer,
|
||||
capabilities,
|
||||
dashboardId,
|
||||
embeddable,
|
||||
closeModal,
|
||||
}: CopyToDashboardModalProps) {
|
||||
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing'>('existing');
|
||||
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const state = {
|
||||
input: omit(embeddable.getInput(), 'id'),
|
||||
type: embeddable.type,
|
||||
};
|
||||
|
||||
const path =
|
||||
dashboardOption === 'existing' && selectedDashboard
|
||||
? `#${createDashboardEditUrl(selectedDashboard.id, true)}`
|
||||
: `#${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`;
|
||||
|
||||
closeModal();
|
||||
stateTransfer.navigateToWithEmbeddablePackage('dashboards', {
|
||||
state,
|
||||
path,
|
||||
});
|
||||
}, [dashboardOption, embeddable, selectedDashboard, stateTransfer, closeModal]);
|
||||
|
||||
return (
|
||||
<PresentationUtilContext>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>{dashboardCopyToDashboardAction.getDisplayName()}</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<>
|
||||
<EuiText>
|
||||
<p>{dashboardCopyToDashboardAction.getDescription()}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiFormRow hasChildLabel={false}>
|
||||
<EuiPanel color="subdued" hasShadow={false} data-test-subj="add-to-dashboard-options">
|
||||
<div>
|
||||
{capabilities.canEditExisting && (
|
||||
<>
|
||||
<EuiRadio
|
||||
checked={dashboardOption === 'existing'}
|
||||
data-test-subj="add-to-existing-dashboard-option"
|
||||
id="existing-dashboard-option"
|
||||
name="dashboard-option"
|
||||
label={dashboardCopyToDashboardAction.getExistingDashboardOption()}
|
||||
onChange={() => setDashboardOption('existing')}
|
||||
/>
|
||||
<div className="savAddDashboard__searchDashboards">
|
||||
<DashboardPicker
|
||||
isDisabled={dashboardOption !== 'existing'}
|
||||
idsToOmit={dashboardId ? [dashboardId] : undefined}
|
||||
onChange={(dashboard) => setSelectedDashboard(dashboard)}
|
||||
/>
|
||||
</div>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
{capabilities.canCreateNew && (
|
||||
<>
|
||||
<EuiRadio
|
||||
checked={dashboardOption === 'new'}
|
||||
data-test-subj="add-to-new-dashboard-option"
|
||||
id="new-dashboard-option"
|
||||
name="dashboard-option"
|
||||
disabled={!dashboardId}
|
||||
label={dashboardCopyToDashboardAction.getNewDashboardOption()}
|
||||
onChange={() => setDashboardOption('new')}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty data-test-subj="cancelCopyToButton" onClick={() => closeModal()}>
|
||||
{dashboardCopyToDashboardAction.getCancelButtonName()}
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton
|
||||
fill
|
||||
data-test-subj="confirmCopyToButton"
|
||||
onClick={onSubmit}
|
||||
disabled={dashboardOption === 'existing' && !selectedDashboard}
|
||||
>
|
||||
{dashboardCopyToDashboardAction.getAcceptButtonName()}
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</PresentationUtilContext>
|
||||
);
|
||||
}
|
|
@ -31,6 +31,11 @@ export {
|
|||
UnlinkFromLibraryActionContext,
|
||||
ACTION_UNLINK_FROM_LIBRARY,
|
||||
} from './unlink_from_library_action';
|
||||
export {
|
||||
CopyToDashboardAction,
|
||||
CopyToDashboardActionContext,
|
||||
ACTION_COPY_TO_DASHBOARD,
|
||||
} from './copy_to_dashboard_action';
|
||||
export {
|
||||
LibraryNotificationActionContext,
|
||||
LibraryNotificationAction,
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
|
@ -48,7 +47,7 @@ export const confirmCreateWithUnsaved = (
|
|||
) => {
|
||||
const session = overlays.openModal(
|
||||
toMountPoint(
|
||||
<EuiModal onClose={() => session.close()}>
|
||||
<>
|
||||
<EuiModalHeader data-test-subj="dashboardCreateConfirm">
|
||||
<EuiModalHeaderTitle>{createConfirmStrings.getCreateTitle()}</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
@ -85,7 +84,7 @@ export const confirmCreateWithUnsaved = (
|
|||
{createConfirmStrings.getContinueButtonText()}
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
</>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'dashboardCreateConfirmModal',
|
||||
|
|
|
@ -75,6 +75,33 @@ export const dashboardFeatureCatalog = {
|
|||
/*
|
||||
Actions
|
||||
*/
|
||||
export const dashboardCopyToDashboardAction = {
|
||||
getDisplayName: () =>
|
||||
i18n.translate('dashboard.panel.copyToDashboard.title', {
|
||||
defaultMessage: 'Copy to dashboard',
|
||||
}),
|
||||
getCancelButtonName: () =>
|
||||
i18n.translate('dashboard.panel.copyToDashboard.cancel', {
|
||||
defaultMessage: 'Cancel',
|
||||
}),
|
||||
getAcceptButtonName: () =>
|
||||
i18n.translate('dashboard.panel.copyToDashboard.goToDashboard', {
|
||||
defaultMessage: 'Copy and go to dashboard',
|
||||
}),
|
||||
getNewDashboardOption: () =>
|
||||
i18n.translate('dashboard.panel.copyToDashboard.newDashboardOptionLabel', {
|
||||
defaultMessage: 'New dashboard',
|
||||
}),
|
||||
getExistingDashboardOption: () =>
|
||||
i18n.translate('dashboard.panel.copyToDashboard.existingDashboardOptionLabel', {
|
||||
defaultMessage: 'Existing dashboard',
|
||||
}),
|
||||
getDescription: () =>
|
||||
i18n.translate('dashboard.panel.copyToDashboard.description', {
|
||||
defaultMessage: "Select where to copy the panel. You're navigated to destination dashboard.",
|
||||
}),
|
||||
};
|
||||
|
||||
export const dashboardAddToLibraryAction = {
|
||||
getDisplayName: () =>
|
||||
i18n.translate('dashboard.panel.AddToLibrary', {
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
import { createKbnUrlTracker } from './services/kibana_utils';
|
||||
import { UsageCollectionSetup } from './services/usage_collection';
|
||||
import { UiActionsSetup, UiActionsStart } from './services/ui_actions';
|
||||
import { PresentationUtilPluginStart } from './services/presentation_util';
|
||||
import { KibanaLegacySetup, KibanaLegacyStart } from './services/kibana_legacy';
|
||||
import { FeatureCatalogueCategory, HomePublicPluginSetup } from './services/home';
|
||||
import { NavigationPublicPluginStart as NavigationStart } from './services/navigation';
|
||||
|
@ -61,6 +62,7 @@ import {
|
|||
UnlinkFromLibraryAction,
|
||||
AddToLibraryAction,
|
||||
LibraryNotificationAction,
|
||||
CopyToDashboardAction,
|
||||
} from './application';
|
||||
import {
|
||||
createDashboardUrlGenerator,
|
||||
|
@ -109,6 +111,7 @@ export interface DashboardStartDependencies {
|
|||
share?: SharePluginStart;
|
||||
uiActions: UiActionsStart;
|
||||
savedObjects: SavedObjectsStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
}
|
||||
|
||||
|
@ -337,8 +340,8 @@ export class DashboardPlugin
|
|||
}
|
||||
|
||||
public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart {
|
||||
const { notifications } = core;
|
||||
const { uiActions, data, share } = plugins;
|
||||
const { notifications, overlays } = core;
|
||||
const { uiActions, data, share, presentationUtil, embeddable } = plugins;
|
||||
|
||||
const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings);
|
||||
|
||||
|
@ -376,6 +379,18 @@ export class DashboardPlugin
|
|||
const libraryNotificationAction = new LibraryNotificationAction(unlinkFromLibraryAction);
|
||||
uiActions.registerAction(libraryNotificationAction);
|
||||
uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id);
|
||||
|
||||
const copyToDashboardAction = new CopyToDashboardAction(
|
||||
overlays,
|
||||
embeddable.getStateTransfer(),
|
||||
{
|
||||
canCreateNew: Boolean(core.application.capabilities.dashboard.createNew),
|
||||
canEditExisting: !Boolean(core.application.capabilities.dashboard.hideWriteControls),
|
||||
},
|
||||
presentationUtil.ContextProvider
|
||||
);
|
||||
uiActions.registerAction(copyToDashboardAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, copyToDashboardAction.id);
|
||||
}
|
||||
|
||||
const savedDashboardLoader = createSavedDashboardLoader({
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { PresentationUtilPluginStart, DashboardPicker } from '../../../presentation_util/public';
|
|
@ -21,6 +21,7 @@
|
|||
{ "path": "../kibana_react/tsconfig.json" },
|
||||
{ "path": "../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../share/tsconfig.json" },
|
||||
{ "path": "../presentation_util/tsconfig.json" },
|
||||
{ "path": "../url_forwarding/tsconfig.json" },
|
||||
{ "path": "../usage_collection/tsconfig.json" },
|
||||
{ "path": "../data/tsconfig.json"},
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"kibanaVersion": "kibana",
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["dashboard", "savedObjects"],
|
||||
"requiredPlugins": ["savedObjects"],
|
||||
"optionalPlugins": []
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { pluginServices } from '../services';
|
|||
export interface DashboardPickerProps {
|
||||
onChange: (dashboard: { name: string; id: string } | null) => void;
|
||||
isDisabled: boolean;
|
||||
idsToOmit?: string[];
|
||||
}
|
||||
|
||||
interface DashboardOption {
|
||||
|
@ -49,7 +50,18 @@ export function DashboardPicker(props: DashboardPickerProps) {
|
|||
}
|
||||
|
||||
if (objects) {
|
||||
setDashboardOptions(objects.map((d) => ({ value: d.id, label: d.attributes.title })));
|
||||
setDashboardOptions(
|
||||
objects
|
||||
.filter((d) => !props.idsToOmit || !props.idsToOmit.includes(d.id))
|
||||
.map((d) => ({
|
||||
value: d.id,
|
||||
label: d.attributes.title,
|
||||
'data-test-subj': `dashboard-picker-option-${d.attributes.title.replaceAll(
|
||||
' ',
|
||||
'-'
|
||||
)}`,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
setIsLoadingDashboards(false);
|
||||
|
@ -60,7 +72,7 @@ export function DashboardPicker(props: DashboardPickerProps) {
|
|||
return () => {
|
||||
cleanedUp = true;
|
||||
};
|
||||
}, [findDashboardsByTitle, query]);
|
||||
}, [findDashboardsByTitle, query, props.idsToOmit]);
|
||||
|
||||
return (
|
||||
<EuiComboBox
|
||||
|
|
|
@ -7,14 +7,17 @@
|
|||
*/
|
||||
|
||||
import { SimpleSavedObject } from 'src/core/public';
|
||||
import { DashboardSavedObject } from 'src/plugins/dashboard/public';
|
||||
import { PluginServices } from './create';
|
||||
import { PartialDashboardAttributes } from './kibana/dashboards';
|
||||
|
||||
export interface PresentationDashboardsService {
|
||||
findDashboards: (
|
||||
query: string,
|
||||
fields: string[]
|
||||
) => Promise<Array<SimpleSavedObject<DashboardSavedObject>>>;
|
||||
findDashboardsByTitle: (title: string) => Promise<Array<SimpleSavedObject<DashboardSavedObject>>>;
|
||||
) => Promise<Array<SimpleSavedObject<PartialDashboardAttributes>>>;
|
||||
findDashboardsByTitle: (
|
||||
title: string
|
||||
) => Promise<Array<SimpleSavedObject<PartialDashboardAttributes>>>;
|
||||
}
|
||||
|
||||
export interface PresentationCapabilitiesService {
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DashboardSavedObject } from 'src/plugins/dashboard/public';
|
||||
|
||||
import { PresentationUtilPluginStartDeps } from '../../types';
|
||||
import { KibanaPluginServiceFactory } from '../create';
|
||||
import { PresentationDashboardsService } from '..';
|
||||
|
@ -17,11 +15,15 @@ export type DashboardsServiceFactory = KibanaPluginServiceFactory<
|
|||
PresentationUtilPluginStartDeps
|
||||
>;
|
||||
|
||||
export interface PartialDashboardAttributes {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const dashboardsServiceFactory: DashboardsServiceFactory = ({ coreStart }) => {
|
||||
const findDashboards = async (query: string = '', fields: string[] = []) => {
|
||||
const { find } = coreStart.savedObjects.client;
|
||||
|
||||
const { savedObjects } = await find<DashboardSavedObject>({
|
||||
const { savedObjects } = await find<PartialDashboardAttributes>({
|
||||
type: 'dashboard',
|
||||
search: `${query}*`,
|
||||
searchFields: fields,
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
"include": ["common/**/*", "public/**/*", "storybook/**/*", "../../../typings/**/*"],
|
||||
"references": [
|
||||
{ "path": "../../core/tsconfig.json" },
|
||||
{ "path": "../dashboard/tsconfig.json" },
|
||||
{ "path": "../saved_objects/tsconfig.json" },
|
||||
]
|
||||
}
|
||||
|
|
126
test/functional/apps/dashboard/copy_panel_to.ts
Normal file
126
test/functional/apps/dashboard/copy_panel_to.ts
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const dashboardVisualizations = getService('dashboardVisualizations');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const find = getService('find');
|
||||
|
||||
const PageObjects = getPageObjects([
|
||||
'header',
|
||||
'common',
|
||||
'discover',
|
||||
'dashboard',
|
||||
'visualize',
|
||||
'timePicker',
|
||||
]);
|
||||
|
||||
const fewPanelsTitle = 'few panels';
|
||||
const markdownTitle = 'Copy To Markdown';
|
||||
let fewPanelsPanelCount = 0;
|
||||
|
||||
const openCopyToModal = async (panelName: string) => {
|
||||
await dashboardPanelActions.openCopyToModalByTitle(panelName);
|
||||
const modalIsOpened = await testSubjects.exists('copyToDashboardPanel');
|
||||
expect(modalIsOpened).to.be(true);
|
||||
const hasDashboardSelector = await testSubjects.exists('add-to-dashboard-options');
|
||||
expect(hasDashboardSelector).to.be(true);
|
||||
};
|
||||
|
||||
describe('dashboard panel copy to', function viewEditModeTests() {
|
||||
before(async function () {
|
||||
await esArchiver.load('dashboard/current/kibana');
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
|
||||
});
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
await PageObjects.dashboard.loadSavedDashboard(fewPanelsTitle);
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
fewPanelsPanelCount = await PageObjects.dashboard.getPanelCount();
|
||||
|
||||
await PageObjects.dashboard.gotoDashboardLandingPage();
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await PageObjects.timePicker.setHistoricalDataRange();
|
||||
await dashboardVisualizations.createAndAddMarkdown({
|
||||
name: markdownTitle,
|
||||
markdown: 'Please add me to some other dashboard',
|
||||
});
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await PageObjects.dashboard.gotoDashboardLandingPage();
|
||||
});
|
||||
|
||||
it('does not show the new dashboard option when on a new dashboard', async () => {
|
||||
await openCopyToModal(markdownTitle);
|
||||
const dashboardSelector = await testSubjects.find('add-to-dashboard-options');
|
||||
const isDisabled = await dashboardSelector.findByCssSelector(
|
||||
`input[id="new-dashboard-option"]:disabled`
|
||||
);
|
||||
expect(isDisabled).not.to.be(null);
|
||||
|
||||
await testSubjects.click('cancelCopyToButton');
|
||||
});
|
||||
|
||||
it('copies a panel to an existing dashboard', async () => {
|
||||
await openCopyToModal(markdownTitle);
|
||||
const dashboardSelector = await testSubjects.find('add-to-dashboard-options');
|
||||
const label = await dashboardSelector.findByCssSelector(
|
||||
`label[for="existing-dashboard-option"]`
|
||||
);
|
||||
await label.click();
|
||||
|
||||
await testSubjects.setValue('dashboardPickerInput', fewPanelsTitle);
|
||||
await testSubjects.existOrFail(`dashboard-picker-option-few-panels`);
|
||||
await find.clickByButtonText(fewPanelsTitle);
|
||||
await testSubjects.click('confirmCopyToButton');
|
||||
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await PageObjects.dashboard.expectOnDashboard(`Editing ${fewPanelsTitle}`);
|
||||
const newPanelCount = await PageObjects.dashboard.getPanelCount();
|
||||
expect(newPanelCount).to.be(fewPanelsPanelCount + 1);
|
||||
});
|
||||
|
||||
it('does not show the current dashboard in the dashboard picker', async () => {
|
||||
await openCopyToModal(markdownTitle);
|
||||
const dashboardSelector = await testSubjects.find('add-to-dashboard-options');
|
||||
const label = await dashboardSelector.findByCssSelector(
|
||||
`label[for="existing-dashboard-option"]`
|
||||
);
|
||||
await label.click();
|
||||
|
||||
await testSubjects.setValue('dashboardPickerInput', fewPanelsTitle);
|
||||
await testSubjects.missingOrFail(`dashboard-picker-option-few-panels`);
|
||||
|
||||
await testSubjects.click('cancelCopyToButton');
|
||||
});
|
||||
|
||||
it('copies a panel to a new dashboard', async () => {
|
||||
await openCopyToModal(markdownTitle);
|
||||
const dashboardSelector = await testSubjects.find('add-to-dashboard-options');
|
||||
const label = await dashboardSelector.findByCssSelector(`label[for="new-dashboard-option"]`);
|
||||
await label.click();
|
||||
await testSubjects.click('confirmCopyToButton');
|
||||
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await PageObjects.dashboard.expectOnDashboard(`Editing New Dashboard (unsaved)`);
|
||||
});
|
||||
|
||||
it('it always appends new panels instead of overwriting', async () => {
|
||||
const newPanelCount = await PageObjects.dashboard.getPanelCount();
|
||||
expect(newPanelCount).to.be(2);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -96,6 +96,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./bwc_shared_urls'));
|
||||
loadTestFile(require.resolve('./panel_replacing'));
|
||||
loadTestFile(require.resolve('./panel_cloning'));
|
||||
loadTestFile(require.resolve('./copy_panel_to'));
|
||||
loadTestFile(require.resolve('./panel_context_menu'));
|
||||
loadTestFile(require.resolve('./dashboard_state'));
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide
|
|||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const browser = getService('browser');
|
||||
const globalNav = getService('globalNav');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
@ -157,6 +158,13 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide
|
|||
await testSubjects.click('breadcrumb dashboardListingBreadcrumb first');
|
||||
}
|
||||
|
||||
public async expectOnDashboard(dashboardTitle: string) {
|
||||
await retry.waitFor(
|
||||
'last breadcrumb to have dashboard title',
|
||||
async () => (await globalNav.getLastBreadcrumb()) === dashboardTitle
|
||||
);
|
||||
}
|
||||
|
||||
public async gotoDashboardLandingPage(ignorePageLeaveWarning = true) {
|
||||
log.debug('gotoDashboardLandingPage');
|
||||
const onPage = await this.onDashboardLandingPage();
|
||||
|
|
|
@ -17,6 +17,7 @@ const TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-togglePanel';
|
|||
const CUSTOMIZE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-ACTION_CUSTOMIZE_PANEL';
|
||||
const OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ = 'embeddablePanelToggleMenuIcon';
|
||||
const OPEN_INSPECTOR_TEST_SUBJ = 'embeddablePanelAction-openInspector';
|
||||
const COPY_PANEL_TO_DATA_TEST_SUBJ = 'embeddablePanelAction-copyToDashboard';
|
||||
const LIBRARY_NOTIFICATION_TEST_SUBJ = 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION';
|
||||
const SAVE_TO_LIBRARY_TEST_SUBJ = 'embeddablePanelAction-saveToLibrary';
|
||||
|
||||
|
@ -148,6 +149,19 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }: Ft
|
|||
await testSubjects.click(CLONE_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async openCopyToModalByTitle(title?: string) {
|
||||
log.debug(`copyPanelTo(${title})`);
|
||||
if (title) {
|
||||
const panelOptions = await this.getPanelHeading(title);
|
||||
await this.openContextMenu(panelOptions);
|
||||
} else {
|
||||
await this.openContextMenu();
|
||||
}
|
||||
const isActionVisible = await testSubjects.exists(COPY_PANEL_TO_DATA_TEST_SUBJ);
|
||||
if (!isActionVisible) await this.clickContextMenuMoreItem();
|
||||
await testSubjects.click(COPY_PANEL_TO_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async openInspectorByTitle(title: string) {
|
||||
const header = await this.getPanelHeading(title);
|
||||
await this.openInspector(header);
|
||||
|
|
|
@ -20,6 +20,10 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('allows to register links into the context menu', async () => {
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
const actionExists = await testSubjects.exists('embeddablePanelAction-samplePanelLink');
|
||||
if (!actionExists) {
|
||||
await dashboardPanelActions.clickContextMenuMoreItem();
|
||||
}
|
||||
const actionElement = await testSubjects.find('embeddablePanelAction-samplePanelLink');
|
||||
const actionElementTag = await actionElement.getTagName();
|
||||
expect(actionElementTag).to.be('a');
|
||||
|
|
|
@ -62,6 +62,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const savedSearchPanel = await testSubjects.find('embeddablePanelHeading-EcommerceData');
|
||||
await dashboardPanelActions.toggleContextMenu(savedSearchPanel);
|
||||
|
||||
const actionExists = await testSubjects.exists('embeddablePanelAction-downloadCsvReport');
|
||||
if (!actionExists) {
|
||||
await dashboardPanelActions.clickContextMenuMoreItem();
|
||||
}
|
||||
await testSubjects.existOrFail('embeddablePanelAction-downloadCsvReport'); // wait for the full panel to display or else the test runner could click the wrong option!
|
||||
await testSubjects.click('embeddablePanelAction-downloadCsvReport');
|
||||
await testSubjects.existOrFail('csvDownloadStarted'); // validate toast panel
|
||||
|
@ -80,6 +84,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
); // panel title is hidden
|
||||
await dashboardPanelActions.toggleContextMenu(savedSearchPanel);
|
||||
|
||||
const actionExists = await testSubjects.exists('embeddablePanelAction-downloadCsvReport');
|
||||
if (!actionExists) {
|
||||
await dashboardPanelActions.clickContextMenuMoreItem();
|
||||
}
|
||||
await testSubjects.existOrFail('embeddablePanelAction-downloadCsvReport');
|
||||
await testSubjects.click('embeddablePanelAction-downloadCsvReport');
|
||||
await testSubjects.existOrFail('csvDownloadStarted');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue