Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -21,7 +21,7 @@ const DASHBOARD_PANEL_GROUP_ORDER = [
'legacyGroup',
];

const DASHBOARD_PANEL_TYPE_COUNT = 24;
const DASHBOARD_PANEL_TYPE_COUNT = 25;

spaceTest.describe(
'Dashboard panel listing (includes observability group)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
import type { DiscoverAppState } from '../../state_management/redux';
import { onSaveDiscoverSession } from './save_discover_session';
import { useDataState } from '../../hooks/use_data_state';
import { TransferAction } from '../../../../plugin_imports/embeddable_editor_service';

/**
* Helper function to build the top nav links
Expand Down Expand Up @@ -275,7 +276,15 @@ export const useTopNavLinks = ({
await onSaveDiscoverSession({
services,
state,
onSaveCb: isEmbeddedEditor ? services.embeddableEditor.transferBackToEditor : undefined,
onSaveCb: isEmbeddedEditor
? (saveState) => {
const action = saveState
? TransferAction.SaveSession
: TransferAction.SaveByValue;

services.embeddableEditor.transferBackToEditor(action, saveState);
}
: undefined,
});
},
popoverWidth: 150,
Expand All @@ -291,7 +300,8 @@ export const useTopNavLinks = ({
items: [
savedAsButton,
{
run: () => services.embeddableEditor.transferBackToEditor(),
run: () =>
services.embeddableEditor.transferBackToEditor(TransferAction.Cancel),
id: 'cancel',
order: 100,
label: i18n.translate('discover.localMenu.cancelTitle', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { i18n } from '@kbn/i18n';
import { v4 as uuidv4 } from 'uuid';
import type { ApplicationStart } from '@kbn/core/public';
import {
type EmbeddableApiContext,
apiCanAccessViewMode,
apiHasAppContext,
apiIsOfType,
getInheritedViewMode,
} from '@kbn/presentation-publishing';

import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import { ADD_PANEL_VISUALIZATION_GROUP, type EmbeddableStart } from '@kbn/embeddable-plugin/public';
import type { DiscoverSessionTab } from '@kbn/saved-search-plugin/common';
import type { DiscoverAppLocator } from '../../../common';
import { ACTION_ADD_DISCOVER_SESSION_PANEL } from '../constants';

export class AddDiscoverSessionPanelAction implements Action<EmbeddableApiContext> {
public id = ACTION_ADD_DISCOVER_SESSION_PANEL;
public readonly type = ACTION_ADD_DISCOVER_SESSION_PANEL;
public readonly order = 45;
public readonly grouping = [ADD_PANEL_VISUALIZATION_GROUP];

constructor(
private readonly application: ApplicationStart,
private readonly locator: DiscoverAppLocator,
private readonly embeddable: EmbeddableStart
) {}

getIconType(): string | undefined {
return 'discoverApp';
}

getDisplayName(): string {
return i18n.translate('discover.savedSearchEmbeddable.action.addPanel.displayName', {
defaultMessage: 'Discover session',
});
}

async execute({
embeddable: embeddableApi,
}: ActionExecutionContext<EmbeddableApiContext>): Promise<void> {
const { app, path } = await this.locator.getLocation({});
const stateTransfer = this.embeddable.getStateTransfer();

const valueInput: DiscoverSessionTab = {
id: uuidv4(),
label: i18n.translate('discover.savedSearchEmbeddable.action.addPanel.byValueTabName', {
defaultMessage: 'New by-value Discover session',
}),
sort: [],
columns: [],
isTextBasedQuery: true,
grid: {},
hideChart: false,
serializedSearchSource: {},
};

const appContext = apiHasAppContext(embeddableApi) ? embeddableApi.getAppContext() : undefined;

stateTransfer.navigateToEditor(app, {
path,
state: {
valueInput,
originatingApp: appContext?.currentAppId || '',
originatingPath: appContext?.getCurrentPath?.(),
},
});
}

getDisplayNameTooltip(): string {
return '';
}

async isCompatible({ embeddable }: ActionExecutionContext<EmbeddableApiContext>) {
const { capabilities } = this.application;
const hasDiscoverPermissions =
(capabilities.discover_v2.show as boolean) || (capabilities.discover_v2.save as boolean);

if (!hasDiscoverPermissions) return false;

return (
apiCanAccessViewMode(embeddable) &&
getInheritedViewMode(embeddable) === 'edit' &&
apiIsOfType(embeddable, 'dashboard')
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const LEGACY_LOG_STREAM_EMBEDDABLE = 'log_stream';

export const ACTION_VIEW_SAVED_SEARCH = 'ACTION_VIEW_SAVED_SEARCH';

export const ACTION_ADD_DISCOVER_SESSION_PANEL = 'ACTION_ADD_DISCOVER_SESSION_PANEL';

export const DEFAULT_HEADER_ROW_HEIGHT_LINES = 3;

/** This constant refers to the dashboard panel specific state */
Expand Down
21 changes: 19 additions & 2 deletions src/platform/plugins/shared/discover/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type { SavedSearchAttributes } from '@kbn/saved-search-plugin/common';
import { i18n } from '@kbn/i18n';
import { once } from 'lodash';
import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics';
import { ON_OPEN_PANEL_MENU } from '@kbn/ui-actions-plugin/common/trigger_ids';
import { ADD_PANEL_TRIGGER, ON_OPEN_PANEL_MENU } from '@kbn/ui-actions-plugin/common/trigger_ids';
import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common';
import { ProjectRoutingAccess } from '@kbn/cps-utils';
import { DISCOVER_APP_LOCATOR, PLUGIN_ID, type DiscoverAppLocator } from '../common';
Expand All @@ -42,7 +42,11 @@ import { registerFeature } from './plugin_imports/register_feature';
import type { UrlTracker } from './build_services';
import { initializeKbnUrlTracking } from './utils/initialize_kbn_url_tracking';
import { defaultCustomizationContext } from './customizations/defaults';
import { ACTION_VIEW_SAVED_SEARCH, LEGACY_LOG_STREAM_EMBEDDABLE } from './embeddable/constants';
import {
ACTION_ADD_DISCOVER_SESSION_PANEL,
ACTION_VIEW_SAVED_SEARCH,
LEGACY_LOG_STREAM_EMBEDDABLE,
} from './embeddable/constants';
import {
DiscoverContainerInternal,
type DiscoverContainerProps,
Expand Down Expand Up @@ -248,6 +252,19 @@ export class DiscoverPlugin
}
);

plugins.uiActions.addTriggerActionAsync(
ADD_PANEL_TRIGGER,
ACTION_ADD_DISCOVER_SESSION_PANEL,
async () => {
const { AddDiscoverSessionPanelAction } = await getEmbeddableServices();
return new AddDiscoverSessionPanelAction(
core.application,
this.locator!,
plugins.embeddable
);
}
);

const isEsqlEnabled = core.uiSettings.get(ENABLE_ESQL);

if (plugins.share && this.locator && isEsqlEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ import type {
import { SEARCH_EMBEDDABLE_TYPE } from '@kbn/discover-utils';
import type { EmbeddableEditorState, EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public';

/**
* Specifies the action to be taken for navigating back to an editor.
*/
export enum TransferAction {
/**
* A Cancel operation. Returns to the editor without modifying the original state.
*/
Cancel,
/**
* A Save Session operation. Updates the saved session and doesn't pass back any serialised state.
*/
SaveSession,
/**
* A Save By Value operation. Sends back to the editor the serialised updated state for the embeddable.
*/
SaveByValue,
}
Comment on lines +16 to +32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach 👌


export class EmbeddableEditorService {
private embeddableState?: EmbeddableEditorState;

Expand All @@ -27,14 +45,29 @@ export class EmbeddableEditorService {
public getByValueInput = (): DiscoverSessionTab | undefined =>
this.embeddableState?.valueInput as DiscoverSessionTab | undefined;

/**
* Resets the embeddable transfer state, ensuring it is cleared in storage and then dropped in memory.
*/
public clearEditorState = () => {
if (this.embeddableState) {
this.embeddableStateTransfer.clearEditorState('discover');
this.embeddableState = undefined;
}
};

public transferBackToEditor = (state?: SavedSearchByValueAttributes) => {
public transferBackToEditor(action: TransferAction.Cancel | TransferAction.SaveSession): void;
public transferBackToEditor(
action: TransferAction.SaveByValue,
state: SavedSearchByValueAttributes
): void;
public transferBackToEditor(action: TransferAction, state?: SavedSearchByValueAttributes): void;
Comment on lines +58 to +63
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're one of few to make good use of function overloading in TS! 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this would be unnecessary if TS had Rust-like enums and pattern-matching 😅

I'm so rust-brained...

/**
* Initiates a navigation back to the editing application, either cancelling the current action to return
* or passing a state for an embeddable to receive an updated view.
*
* **NOTE**: Cancelling will never pass an updated state, so the state param is ignored for cancel actions.
*/
public transferBackToEditor(action: TransferAction, state?: SavedSearchByValueAttributes) {
if (this.embeddableState) {
const app = this.embeddableState.originatingApp;
const path = this.embeddableState.originatingPath;
Expand All @@ -43,15 +76,18 @@ export class EmbeddableEditorService {
this.embeddableStateTransfer.clearEditorState('discover');
this.embeddableStateTransfer.navigateToWithEmbeddablePackages(app, {
path,
state: [
{
type: SEARCH_EMBEDDABLE_TYPE,
serializedState: { attributes: state },
embeddableId: this.embeddableState?.embeddableId,
},
],
state:
action !== TransferAction.Cancel
? [
{
type: SEARCH_EMBEDDABLE_TYPE,
serializedState: { attributes: state },
embeddableId: this.embeddableState?.embeddableId,
},
]
: [],
});
}
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

export { ViewSavedSearchAction } from '../embeddable/actions/view_saved_search_action';
export { AddDiscoverSessionPanelAction } from '../embeddable/actions/add_discover_session_panel_action';
export { addPanelFromLibrary } from '../embeddable/utils/add_panel_from_library';
export { getSearchEmbeddableFactory } from '../embeddable/get_search_embeddable_factory';
export { getLegacyLogStreamEmbeddableFactory } from '../embeddable/get_legacy_log_stream_embeddable_factory';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import { i18n } from '@kbn/i18n';
import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
import type { DiscoverServices } from '../build_services';
import type { EmbeddableEditorService } from '../plugin_imports/embeddable_editor_service';
import {
TransferAction,
type EmbeddableEditorService,
} from '../plugin_imports/embeddable_editor_service';

const rootPath = '#/';

Expand All @@ -36,7 +39,9 @@ function getRootBreadcrumbs({
}),
deepLinkId: isEmbeddedEditor ? 'dashboards' : 'discover',
href,
onClick: isEmbeddedEditor ? () => embeddable.transferBackToEditor() : undefined,
onClick: isEmbeddedEditor
? () => embeddable.transferBackToEditor(TransferAction.Cancel)
: undefined,
},
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
]);

// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
expect(panelTypes.length).to.eql(14);
expect(panelTypes.length).to.eql(15);
});
});
}
Loading
Loading