Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow replacing visualizations on dashboard #45095

Merged
merged 34 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
66fe6d1
First version of change view functionality
friol Sep 6, 2019
fd67640
Adds the 'replace view' functionality to dashboard edit mode
friol Sep 7, 2019
7d9ec00
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 17, 2019
352d771
Merge branch 'changeview' of https://github.com/friol/kibana into cha…
friol Sep 17, 2019
ac273a3
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 17, 2019
6cf0f5b
Fixed type_check errors
friol Sep 17, 2019
c06558f
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 18, 2019
5ce9f56
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 19, 2019
07390f3
Changes to adapt to PR #44707
friol Sep 19, 2019
2ae9521
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 23, 2019
7d98a45
Fixed relative paths to pass type_check tests
friol Sep 23, 2019
ee423de
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 24, 2019
4ace431
Changes requested by @stacey-gammon
friol Sep 24, 2019
6485189
Merge remote-tracking branch 'upstream/master' into changeview
friol Sep 26, 2019
5b0fef1
Changes requested by @stacey-gammon (action is now part of dashboard_…
friol Sep 27, 2019
ef29e15
Merge branch 'master' into changeview
elasticmachine Sep 30, 2019
089d336
Merge remote-tracking branch 'upstream/master' into changeview
friol Oct 3, 2019
17b16e4
Fixed import paths for type check errors
friol Oct 3, 2019
7d8cd89
Merge branch 'changeview' of https://github.com/friol/kibana into cha…
friol Oct 3, 2019
736e3e4
Fixed i18n errors
friol Oct 4, 2019
912faa1
Merge branch 'master' into changeview
elasticmachine Oct 7, 2019
a858d6a
Merge remote-tracking branch 'upstream/master' into changeview
friol Oct 8, 2019
99ccd39
Merge branch 'changeview' of https://github.com/friol/kibana into cha…
friol Oct 8, 2019
50940c2
Changes requested in review and jest tests
friol Oct 9, 2019
628cdfa
Merge branch 'master' into changeview
elasticmachine Oct 10, 2019
94b5f7a
Merge branch 'master' into changeview
elasticmachine Oct 16, 2019
00d234f
Merge remote-tracking branch 'upstream/master' into changeview
friol Oct 16, 2019
46abe9d
Renamed menu action to 'Replace panel', adjusted the jest test to pas…
friol Oct 16, 2019
22a6007
Merge branch 'changeview' of https://github.com/friol/kibana into cha…
friol Oct 16, 2019
e153651
refactor: update action naming
nickofthyme Oct 18, 2019
9bb2646
test: add functional tests to pr 45095
nickofthyme Oct 18, 2019
8034d10
Merge branch 'upstream/master' into changeview
nickofthyme Oct 18, 2019
e8dceea
test: isolate replace panel tests, add saved search replacement
nickofthyme Oct 18, 2019
61b2ade
Merge branch 'master' into changeview
elasticmachine Oct 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { InspectPanelAction } from './panel_header/panel_actions/inspect_panel_a
import { EditPanelAction, Action } from '../actions';
import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal';
import { Start as InspectorStartContract } from '../../../../../../../../plugins/inspector/public';
import { ChangeViewAction } from './panel_header/panel_actions';

interface Props {
embeddable: IEmbeddable<any, any>;
Expand Down Expand Up @@ -221,6 +222,13 @@ export class EmbeddablePanel extends React.Component<Props, State> {
this.props.SavedObjectFinder
),
new InspectPanelAction(this.props.inspector),
new ChangeViewAction(
this.props.getEmbeddableFactory,
this.props.getAllEmbeddableFactories,
this.props.overlays,
this.props.notifications,
this.props.SavedObjectFinder
),
new RemovePanelAction(),
new EditPanelAction(this.props.getEmbeddableFactory),
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { CoreSetup } from 'src/core/public';
import { DashboardPanelState } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public';

import {
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
// @ts-ignore
EuiSuperSelect,
friol marked this conversation as resolved.
Show resolved Hide resolved
EuiTitle,
} from '@elastic/eui';

import { IContainer } from '../../../../containers';
import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types';
import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from '../../../../embeddables';

interface Props {
onClose: () => void;
container: IContainer;
getFactory: GetEmbeddableFactory;
getAllFactories: GetEmbeddableFactories;
notifications: CoreSetup['notifications'];
SavedObjectFinder: React.ComponentType<any>;
viewToRemove: IEmbeddable<EmbeddableInput, EmbeddableOutput>;
}

export class ChangeViewFlyout extends React.Component<Props> {
private lastToast: any;

constructor(props: Props) {
super(props);
}

public showToast = (name: string) => {
// To avoid the clutter of having toast messages cover flyout
// close previous toast message before creating a new one
if (this.lastToast) {
this.props.notifications.toasts.remove(this.lastToast);
}

this.lastToast = this.props.notifications.toasts.addSuccess({
title: i18n.translate(
'embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle',
{
defaultMessage: '{savedObjectName} was added',
values: {
savedObjectName: name,
},
}
),
'data-test-subj': 'addObjectToContainerSuccess',
});
};

public onChangeView = async (id: string, type: string, name: string) => {
const originalPanels = this.props.container.getInput().panels;
const filteredPanels = { ...originalPanels };

const nnw = (filteredPanels[this.props.viewToRemove.id] as DashboardPanelState).gridData.w;
friol marked this conversation as resolved.
Show resolved Hide resolved
const nnh = (filteredPanels[this.props.viewToRemove.id] as DashboardPanelState).gridData.h;
const nnx = (filteredPanels[this.props.viewToRemove.id] as DashboardPanelState).gridData.x;
const nny = (filteredPanels[this.props.viewToRemove.id] as DashboardPanelState).gridData.y;

// add the new view
const newObj = await this.props.container.addSavedObjectEmbeddable(type, id);

const finalPanels = this.props.container.getInput().panels;
(finalPanels[newObj.id] as DashboardPanelState).gridData.w = nnw;
(finalPanels[newObj.id] as DashboardPanelState).gridData.h = nnh;
(finalPanels[newObj.id] as DashboardPanelState).gridData.x = nnx;
(finalPanels[newObj.id] as DashboardPanelState).gridData.y = nny;

// delete the old view
delete finalPanels[this.props.viewToRemove.id];

// apply changes
this.props.container.updateInput(finalPanels);
this.props.container.reload();

this.showToast(name);
this.props.onClose();
};

public render() {
const SavedObjectFinder = this.props.SavedObjectFinder;
const savedObjectsFinder = (
<SavedObjectFinder
onChoose={this.onChangeView}
savedObjectMetaData={[...this.props.getAllFactories()]
.filter(
embeddableFactory =>
Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType
)
.map(({ savedObjectMetaData }) => savedObjectMetaData as any)}
showFilter={true}
noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', {
defaultMessage: 'No matching objects found.',
})}
/>
);

const vtr = 'Replace view ' + this.props.viewToRemove.getTitle() + ' with...';

return (
<EuiFlyout ownFocus onClose={this.props.onClose} data-test-subj="dashboardChangeView">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>
<span>{vtr}</span>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>{savedObjectsFinder}</EuiFlyoutBody>
</EuiFlyout>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 { IContainer } from '../../../../containers';
import { ChangeViewFlyout } from './change_view_flyout';
import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types';
import { NotificationsStart } from '../../../../../../../../../../../core/public';
import { KibanaReactOverlays } from '../../../../../../../../../../../plugins/kibana_react/public';
import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from '../../../../embeddables';

export async function openChangeViewFlyout(options: {
embeddable: IContainer;
getFactory: GetEmbeddableFactory;
getAllFactories: GetEmbeddableFactories;
overlays: KibanaReactOverlays;
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType<any>;
viewToRemove: IEmbeddable<EmbeddableInput, EmbeddableOutput>;
}) {
const {
embeddable,
getFactory,
getAllFactories,
overlays,
notifications,
SavedObjectFinder,
viewToRemove,
} = options;
const flyoutSession = overlays.openFlyout(
<ChangeViewFlyout
container={embeddable}
onClose={() => {
if (flyoutSession) {
flyoutSession.close();
}
}}
getFactory={getFactory}
getAllFactories={getAllFactories}
notifications={notifications}
SavedObjectFinder={SavedObjectFinder}
viewToRemove={viewToRemove}
/>,
{
'data-test-subj': 'changeViewFlyout',
}
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { ContainerInput, IContainer } from '../../../containers';
import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../../../types';
import { Action } from '../../../actions';
import { NotificationsStart } from '../../../../../../../../../../core/public';
import { KibanaReactOverlays } from '../../../../../../../../../../plugins/kibana_react/public';
import { openChangeViewFlyout } from '../../../panel/panel_header/panel_actions/add_panel/open_change_view_flyout';
import { IEmbeddable } from '../../../embeddables';

export const CHANGE_VIEW_ACTION = 'changeView';

interface ExpandedPanelInput extends ContainerInput {
expandedPanelId: string;
}

interface ActionContext {
embeddable: IEmbeddable;
}

function hasExpandedPanelInput(
container: IContainer
): container is IContainer<{}, ExpandedPanelInput> {
return (container as IContainer<{}, ExpandedPanelInput>).getInput().expandedPanelId !== undefined;
}

export class ChangeViewAction extends Action {
public readonly type = CHANGE_VIEW_ACTION;
constructor(
friol marked this conversation as resolved.
Show resolved Hide resolved
private readonly getFactory: GetEmbeddableFactory,
private readonly getAllFactories: GetEmbeddableFactories,
private readonly overlays: KibanaReactOverlays,
private readonly notifications: NotificationsStart,
private readonly SavedObjectFinder: React.ComponentType<any>
) {
super(CHANGE_VIEW_ACTION);
this.order = 11;
}

public getDisplayName() {
return i18n.translate('embeddableApi.panel.removePanel.replaceView', {
defaultMessage: 'Replace visualization',
});
}

public getIconType() {
return 'kqlOperand';
}

public async isCompatible({ embeddable }: ActionContext) {
const isPanelExpanded =
embeddable.parent &&
hasExpandedPanelInput(embeddable.parent) &&
embeddable.parent.getInput().expandedPanelId === embeddable.id;

return Boolean(
friol marked this conversation as resolved.
Show resolved Hide resolved
embeddable.parent && embeddable.getInput().viewMode === ViewMode.EDIT && !isPanelExpanded
);
}

public async execute({ embeddable }: ActionContext) {
if (embeddable.parent) {
friol marked this conversation as resolved.
Show resolved Hide resolved
const view = embeddable;
const dash = embeddable.parent;

if (dash) {
if (!dash.getIsContainer()) {
throw new Error('Context is incompatible');
}

if (embeddable) {
openChangeViewFlyout({
embeddable: dash,
getFactory: this.getFactory,
getAllFactories: this.getAllFactories,
overlays: this.overlays,
notifications: this.notifications,
SavedObjectFinder: this.SavedObjectFinder,
viewToRemove: view,
});
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
export { InspectPanelAction } from './inspect_panel_action';
export { ADD_PANEL_ACTION_ID, AddPanelAction, openAddPanelFlyout } from './add_panel';
export { RemovePanelAction } from './remove_panel_action';
export { ChangeViewAction } from './change_view_action';