[dashboard] replace lens vis alias with lens add panel action (#210478)

Remove visualizations dependency from dashboard plugin. Instead of using
lens visTypeAlias, navigating to lens is done by executing addLensPanel
action.
This commit is contained in:
Nathan Reese 2025-02-12 08:18:47 -07:00 committed by GitHub
parent d9d0b39272
commit fbce75620c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 63 additions and 115 deletions

View file

@ -21,7 +21,8 @@ export interface HasAppContext {
export const apiHasAppContext = (unknownApi: unknown): unknownApi is HasAppContext => {
return (
(unknownApi as HasAppContext).getAppContext !== undefined &&
Boolean(unknownApi) &&
(unknownApi as HasAppContext)?.getAppContext !== undefined &&
typeof (unknownApi as HasAppContext).getAppContext === 'function'
);
};

View file

@ -26,7 +26,6 @@
"uiActions",
"urlForwarding",
"presentationUtil",
"visualizations",
"unifiedSearch"
],
"optionalPlugins": [

View file

@ -0,0 +1,29 @@
/*
* 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 { ActionExecutionContext, addPanelMenuTrigger } from '@kbn/ui-actions-plugin/public';
import { i18n } from '@kbn/i18n';
import { coreServices, uiActionsService } from '../services/kibana_services';
import { DashboardApi } from '../dashboard_api/types';
export async function executeAddLensPanelAction(dashboardApi: DashboardApi) {
try {
const addLensPanelAction = await uiActionsService.getAction('addLensPanelAction');
addLensPanelAction.execute({
embeddable: dashboardApi,
trigger: addPanelMenuTrigger,
} as ActionExecutionContext);
} catch (error) {
coreServices.notifications.toasts.addWarning(
i18n.translate('dashboard.addNewPanelError', {
defaultMessage: 'Unable to add new panel',
})
);
}
}

View file

@ -1,56 +0,0 @@
/*
* 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 type { HasAppContext } from '@kbn/presentation-publishing';
import type { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import {
dataService,
embeddableService,
usageCollectionService,
} from '../../../services/kibana_services';
import { DASHBOARD_UI_METRIC_ID } from '../../../utils/telemetry_constants';
export function navigateToVisEditor(api: HasAppContext, visType?: BaseVisType | VisTypeAlias) {
let path = '';
let appId = '';
if (visType) {
const trackUiMetric = usageCollectionService?.reportUiCounter.bind(
usageCollectionService,
DASHBOARD_UI_METRIC_ID
);
if (trackUiMetric) {
trackUiMetric(METRIC_TYPE.CLICK, `${visType.name}:create`);
}
if (!('alias' in visType)) {
// this visualization is not an alias
appId = 'visualize';
path = `#/create?type=${encodeURIComponent(visType.name)}`;
} else if (visType.alias && 'path' in visType.alias) {
// this visualization **is** an alias, and it has an app to redirect to for creation
appId = visType.alias.app;
path = visType.alias.path;
}
} else {
appId = 'visualize';
path = '#/create?';
}
const stateTransferService = embeddableService.getStateTransfer();
stateTransferService.navigateToEditor(appId, {
path,
state: {
originatingApp: api.getAppContext()?.currentAppId,
originatingPath: api.getAppContext()?.getCurrentPath?.(),
searchSessionId: dataService.search.session.getSessionId(),
},
});
}

View file

@ -9,28 +9,25 @@
import { useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useCallback } from 'react';
import React, { useState } from 'react';
import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar';
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
import useMountedState from 'react-use/lib/useMountedState';
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
import { visualizationsService } from '../../services/kibana_services';
import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings';
import { ControlsToolbarButton } from './controls_toolbar_button';
import { AddPanelButton } from './add_panel_button/components/add_panel_button';
import { addFromLibrary } from '../../dashboard_container/embeddable/api';
import { navigateToVisEditor } from './add_panel_button/navigate_to_vis_editor';
import { executeAddLensPanelAction } from '../../dashboard_actions/execute_add_lens_panel_action';
export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }) {
const { euiTheme } = useEuiTheme();
const isMounted = useMountedState();
const dashboardApi = useDashboardApi();
const navigateToDefaultEditor = useCallback(() => {
const lensAlias = visualizationsService.getAliases().find(({ name }) => name === 'lens');
navigateToVisEditor(dashboardApi, lensAlias);
}, [dashboardApi]);
const [isLoading, setIsLoading] = useState(false);
const controlGroupApi = useStateFromPublishingSubject(dashboardApi.controlGroupApi$);
const extraButtons = [
@ -55,10 +52,17 @@ export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }
primaryButton: (
<ToolbarButton
type="primary"
isDisabled={isDisabled}
isDisabled={isDisabled || isLoading}
isLoading={isLoading}
iconType="lensApp"
size="s"
onClick={navigateToDefaultEditor}
onClick={async () => {
setIsLoading(true);
await executeAddLensPanelAction(dashboardApi);
if (isMounted()) {
setIsLoading(false);
}
}}
label={getCreateVisualizationButtonTitle()}
data-test-subj="dashboardAddNewPanelButton"
/>

View file

@ -13,13 +13,11 @@ import React from 'react';
import { DashboardContext } from '../../../dashboard_api/use_dashboard_api';
import { DashboardApi } from '../../../dashboard_api/types';
import { coreServices, visualizationsService } from '../../../services/kibana_services';
import { coreServices } from '../../../services/kibana_services';
import { DashboardEmptyScreen } from './dashboard_empty_screen';
import { ViewMode } from '@kbn/presentation-publishing';
import { BehaviorSubject } from 'rxjs';
visualizationsService.getAliases = jest.fn().mockReturnValue([{ name: 'lens' }]);
describe('DashboardEmptyScreen', () => {
function mountComponent(viewMode: ViewMode) {
const mockDashboardApi = {

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { useCallback, useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n';
import {
@ -19,58 +19,29 @@ import {
EuiPageTemplate,
EuiText,
} from '@elastic/eui';
import { METRIC_TYPE } from '@kbn/analytics';
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
import useMountedState from 'react-use/lib/useMountedState';
import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api';
import { DASHBOARD_UI_METRIC_ID } from '../../../utils/telemetry_constants';
import {
coreServices,
dataService,
embeddableService,
usageCollectionService,
visualizationsService,
} from '../../../services/kibana_services';
import { coreServices } from '../../../services/kibana_services';
import { getDashboardCapabilities } from '../../../utils/get_dashboard_capabilities';
import { addFromLibrary } from '../../embeddable/api';
import { executeAddLensPanelAction } from '../../../dashboard_actions/execute_add_lens_panel_action';
export function DashboardEmptyScreen() {
const lensAlias = useMemo(
() => visualizationsService.getAliases().find(({ name }) => name === 'lens'),
[]
);
const { showWriteControls } = useMemo(() => {
return getDashboardCapabilities();
}, []);
const isMounted = useMountedState();
const dashboardApi = useDashboardApi();
const [isLoading, setIsLoading] = useState(false);
const isDarkTheme = useObservable(coreServices.theme.theme$)?.darkMode;
const viewMode = useStateFromPublishingSubject(dashboardApi.viewMode$);
const isEditMode = useMemo(() => {
return viewMode === 'edit';
}, [viewMode]);
const goToLens = useCallback(() => {
if (!lensAlias || !lensAlias.alias) return;
const trackUiMetric = usageCollectionService?.reportUiCounter.bind(
usageCollectionService,
DASHBOARD_UI_METRIC_ID
);
if (trackUiMetric) {
trackUiMetric(METRIC_TYPE.CLICK, `${lensAlias.name}:create`);
}
const appContext = dashboardApi.getAppContext();
embeddableService.getStateTransfer().navigateToEditor(lensAlias.alias.app, {
path: lensAlias.alias.path,
state: {
originatingApp: appContext?.currentAppId,
originatingPath: appContext?.getCurrentPath?.() ?? '',
searchSessionId: dataService.search.session.getSessionId(),
},
});
}, [lensAlias, dashboardApi]);
// TODO replace these SVGs with versions from EuiIllustration as soon as it becomes available.
const imageUrl = coreServices.http.basePath.prepend(
`/plugins/dashboard/assets/${isDarkTheme ? 'dashboards_dark' : 'dashboards_light'}.svg`
@ -123,7 +94,17 @@ export function DashboardEmptyScreen() {
return (
<EuiFlexGroup justifyContent="center" gutterSize="l" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton iconType="lensApp" onClick={() => goToLens()}>
<EuiButton
isLoading={isLoading}
iconType="lensApp"
onClick={async () => {
setIsLoading(true);
await executeAddLensPanelAction(dashboardApi);
if (isMounted()) {
setIsLoading(false);
}
}}
>
{i18n.translate('dashboard.emptyScreen.createVisualization', {
defaultMessage: 'Create visualization',
})}

View file

@ -59,7 +59,6 @@ import type {
UsageCollectionSetup,
UsageCollectionStart,
} from '@kbn/usage-collection-plugin/public';
import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
import {
@ -119,7 +118,6 @@ export interface DashboardStartDependencies {
unifiedSearch: UnifiedSearchPublicPluginStart;
urlForwarding: UrlForwardingStart;
usageCollection?: UsageCollectionStart;
visualizations: VisualizationsStart;
customBranding: CustomBrandingStart;
serverless?: ServerlessPluginStart;
noDataPage?: NoDataPagePluginStart;

View file

@ -28,7 +28,6 @@ import type { SpacesApi } from '@kbn/spaces-plugin/public';
import type { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import type { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
import type { DashboardStartDependencies } from '../plugin';
@ -51,7 +50,6 @@ export let spacesService: SpacesApi | undefined;
export let uiActionsService: UiActionsPublicStart;
export let urlForwardingService: UrlForwardingStart;
export let usageCollectionService: UsageCollectionStart | undefined;
export let visualizationsService: VisualizationsStart;
const servicesReady$ = new BehaviorSubject(false);
@ -75,7 +73,6 @@ export const setKibanaServices = (kibanaCore: CoreStart, deps: DashboardStartDep
uiActionsService = deps.uiActions;
urlForwardingService = deps.urlForwarding;
usageCollectionService = deps.usageCollection;
visualizationsService = deps.visualizations;
servicesReady$.next(true);
};

View file

@ -29,7 +29,6 @@ import { savedObjectTaggingOssPluginMock } from '@kbn/saved-objects-tagging-oss-
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
import { urlForwardingPluginMock } from '@kbn/url-forwarding-plugin/public/mocks';
import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks';
import { setKibanaServices } from './kibana_services';
import { setLogger } from './logger';
@ -72,7 +71,6 @@ export const setStubKibanaServices = () => {
unifiedSearch: unifiedSearchPluginMock.createStartContract(),
urlForwarding: urlForwardingPluginMock.createStartContract(),
usageCollection: usageCollectionPluginMock.createSetupContract(),
visualizations: visualizationsPluginMock.createStartContract(),
});
};

View file

@ -23,7 +23,6 @@
"@kbn/saved-objects-plugin",
"@kbn/screenshot-mode-plugin",
"@kbn/ui-actions-plugin",
"@kbn/visualizations-plugin",
"@kbn/spaces-plugin",
"@kbn/config-schema",
"@kbn/utility-types",