diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 10c2f644b16b1..53ad12d74a373 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -74,6 +74,7 @@ export { type PublishesWritablePanelDescription, } from './interfaces/publishes_panel_description'; export { + getPanelTitle, apiPublishesPanelTitle, apiPublishesWritablePanelTitle, useDefaultPanelTitle, diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.test.ts b/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.test.ts new file mode 100644 index 0000000000000..df8d1a0d9a916 --- /dev/null +++ b/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.test.ts @@ -0,0 +1,36 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; +import { getPanelTitle } from './publishes_panel_title'; + +describe('getPanelTitle', () => { + test('should return default title when title is undefined', () => { + const api = { + panelTitle: new BehaviorSubject(undefined), + defaultPanelTitle: new BehaviorSubject('default title'), + }; + expect(getPanelTitle(api)).toBe('default title'); + }); + + test('should return default title when title is empty string', () => { + const api = { + panelTitle: new BehaviorSubject(''), + defaultPanelTitle: new BehaviorSubject('default title'), + }; + expect(getPanelTitle(api)).toBe('default title'); + }); + + test('should return title when title is provided', () => { + const api = { + panelTitle: new BehaviorSubject('custom title'), + defaultPanelTitle: new BehaviorSubject('default title'), + }; + expect(getPanelTitle(api)).toBe('custom title'); + }); +}); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts b/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts index b1b10fb1c742a..29d82c4105816 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts @@ -14,6 +14,10 @@ export interface PublishesPanelTitle { defaultPanelTitle?: PublishingSubject; } +export function getPanelTitle(api: Partial): string | undefined { + return api.panelTitle?.value || api.defaultPanelTitle?.value; +} + export type PublishesWritablePanelTitle = PublishesPanelTitle & { setPanelTitle: (newTitle: string | undefined) => void; setHidePanelTitle: (hide: boolean | undefined) => void; @@ -44,8 +48,11 @@ export const apiPublishesWritablePanelTitle = ( /** * A hook that gets this API's panel title as a reactive variable which will cause re-renders on change. */ -export const usePanelTitle = (api: Partial | undefined) => - useStateFromPublishingSubject(api?.panelTitle); +export const usePanelTitle = (api: Partial | undefined) => { + const title = useStateFromPublishingSubject(api?.panelTitle); + const defaultTitle = useStateFromPublishingSubject(api?.defaultPanelTitle); + return title || defaultTitle; +}; /** * A hook that gets this API's hide panel title setting as a reactive variable which will cause re-renders on change. diff --git a/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.tsx b/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.tsx index c9ee01f358bdf..bd4eeb5694b8a 100644 --- a/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/add_to_library_action.tsx @@ -10,6 +10,7 @@ import { apiCanLinkToLibrary, CanLinkToLibrary } from '@kbn/presentation-library import { apiCanAccessViewMode, EmbeddableApiContext, + getPanelTitle, PublishesPanelTitle, CanAccessViewMode, getInheritedViewMode, @@ -57,7 +58,7 @@ export class AddToLibraryAction implements Action { public async execute({ embeddable }: EmbeddableApiContext) { if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); - const panelTitle = embeddable.panelTitle?.value ?? embeddable.defaultPanelTitle?.value; + const panelTitle = getPanelTitle(embeddable); try { await embeddable.linkToLibrary(); this.toastsService.addSuccess({ diff --git a/src/plugins/dashboard/public/dashboard_actions/export_csv_action.tsx b/src/plugins/dashboard/public/dashboard_actions/export_csv_action.tsx index 22be8fd853cd5..45ec7c7db8013 100644 --- a/src/plugins/dashboard/public/dashboard_actions/export_csv_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/export_csv_action.tsx @@ -17,7 +17,11 @@ import { HasInspectorAdapters, type Adapters, } from '@kbn/inspector-plugin/public'; -import { EmbeddableApiContext, PublishesPanelTitle } from '@kbn/presentation-publishing'; +import { + EmbeddableApiContext, + getPanelTitle, + PublishesPanelTitle, +} from '@kbn/presentation-publishing'; import { pluginServices } from '../services/plugin_services'; import { dashboardExportCsvActionStrings } from './_dashboard_actions_strings'; @@ -98,7 +102,7 @@ export class ExportCSVAction implements Action { const postFix = datatables.length > 1 ? `-${i + 1}` : ''; const untitledFilename = dashboardExportCsvActionStrings.getUntitledFilename(); - memo[`${embeddable.panelTitle?.value || untitledFilename}${postFix}.csv`] = { + memo[`${getPanelTitle(embeddable) || untitledFilename}${postFix}.csv`] = { content: exporters.datatableToCSV(datatable, { csvSeparator: this.uiSettings.get('csv:separator', ','), quoteValues: this.uiSettings.get('csv:quoteValues', true), diff --git a/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx index 797a61647e919..6d3de4c1e4c71 100644 --- a/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx @@ -10,7 +10,7 @@ import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; import React from 'react'; import { Toast } from '@kbn/core/public'; - +import { getPanelTitle } from '@kbn/presentation-publishing'; import { pluginServices } from '../services/plugin_services'; import { ReplacePanelActionApi } from './replace_panel_action'; import { dashboardReplacePanelActionStrings } from './_dashboard_actions_strings'; @@ -83,9 +83,7 @@ export class ReplacePanelFlyout extends React.Component {

- {dashboardReplacePanelActionStrings.getFlyoutHeader( - this.props.api.panelTitle?.value ?? this.props.api.defaultPanelTitle?.value - )} + {dashboardReplacePanelActionStrings.getFlyoutHeader(getPanelTitle(this.props.api))}

diff --git a/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx b/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx index 1cf9803207d30..f8184a7738119 100644 --- a/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx @@ -14,6 +14,7 @@ import { CanAccessViewMode, EmbeddableApiContext, getInheritedViewMode, + getPanelTitle, PublishesPanelTitle, } from '@kbn/presentation-publishing'; import { pluginServices } from '../services/plugin_services'; @@ -60,7 +61,7 @@ export class UnlinkFromLibraryAction implements Action { public async execute({ embeddable }: EmbeddableApiContext) { if (!unlinkActionIsCompatible(embeddable)) throw new IncompatibleActionError(); - const title = embeddable.panelTitle?.value ?? embeddable.defaultPanelTitle?.value; + const title = getPanelTitle(embeddable); try { await embeddable.unlinkFromLibrary(); this.toastsService.addSuccess({ diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts index 9dc32bf94c54f..03cf8aa905138 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts @@ -11,7 +11,7 @@ import { PanelNotFoundError, reactEmbeddableRegistryHasKey, } from '@kbn/embeddable-plugin/public'; -import { apiPublishesPanelTitle } from '@kbn/presentation-publishing'; +import { apiPublishesPanelTitle, getPanelTitle } from '@kbn/presentation-publishing'; import { filter, map, max } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; import { DashboardPanelState } from '../../../../common'; @@ -59,9 +59,7 @@ const duplicateReactEmbeddableInput = async ( const child = dashboard.reactEmbeddableChildren.value[idToDuplicate]; if (!child) throw new PanelNotFoundError(); - const lastTitle = apiPublishesPanelTitle(child) - ? child.panelTitle.value ?? child.defaultPanelTitle?.value ?? '' - : ''; + const lastTitle = apiPublishesPanelTitle(child) ? getPanelTitle(child) ?? '' : ''; const newTitle = await incrementPanelTitle(dashboard, lastTitle); const id = uuidv4(); const serializedState = await child.serializeState(); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 868c1b8459229..22eb43994911c 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -10,6 +10,7 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { Reference } from '@kbn/content-management-utils'; import type { ControlGroupContainer } from '@kbn/controls-plugin/public'; import type { KibanaExecutionContext, OverlayRef } from '@kbn/core/public'; +import { getPanelTitle } from '@kbn/presentation-publishing'; import { RefreshInterval } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; @@ -664,10 +665,7 @@ export class DashboardContainer for (const [id, panel] of Object.entries(this.getInput().panels)) { const title = await (async () => { if (reactEmbeddableRegistryHasKey(panel.type)) { - return ( - this.reactEmbeddableChildren.value[id]?.panelTitle?.value ?? - this.reactEmbeddableChildren.value[id]?.defaultPanelTitle?.value - ); + return getPanelTitle(this.reactEmbeddableChildren.value[id]); } await this.untilEmbeddableLoaded(id); const child: IEmbeddable = this.getChild(id); diff --git a/src/plugins/presentation_panel/public/panel_actions/customize_panel_action/customize_panel_editor.tsx b/src/plugins/presentation_panel/public/panel_actions/customize_panel_action/customize_panel_editor.tsx index 9a028b4000952..4cae2db5dcea9 100644 --- a/src/plugins/presentation_panel/public/panel_actions/customize_panel_action/customize_panel_editor.tsx +++ b/src/plugins/presentation_panel/public/panel_actions/customize_panel_action/customize_panel_editor.tsx @@ -29,7 +29,11 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; -import { apiPublishesLocalUnifiedSearch, getInheritedViewMode } from '@kbn/presentation-publishing'; +import { + apiPublishesLocalUnifiedSearch, + getInheritedViewMode, + getPanelTitle, +} from '@kbn/presentation-publishing'; import { core } from '../../kibana_services'; import { CustomizePanelActionApi } from './customize_panel_action'; @@ -59,9 +63,7 @@ export const CustomizePanelEditor = ({ const [panelDescription, setPanelDescription] = useState( api.panelDescription?.value ?? api.defaultPanelDescription?.value ); - const [panelTitle, setPanelTitle] = useState( - api.panelTitle?.value ?? api.defaultPanelTitle?.value - ); + const [panelTitle, setPanelTitle] = useState(getPanelTitle(api)); const [localTimeRange, setLocalTimeRange] = useState( api.localTimeRange?.value ?? api?.getFallbackTimeRange?.() ); diff --git a/src/plugins/presentation_panel/public/panel_actions/inspect_panel_action/inspect_panel_action.ts b/src/plugins/presentation_panel/public/panel_actions/inspect_panel_action/inspect_panel_action.ts index 0ad8fa48f9186..36596ca5d2e59 100644 --- a/src/plugins/presentation_panel/public/panel_actions/inspect_panel_action/inspect_panel_action.ts +++ b/src/plugins/presentation_panel/public/panel_actions/inspect_panel_action/inspect_panel_action.ts @@ -11,6 +11,7 @@ import { apiHasInspectorAdapters, HasInspectorAdapters } from '@kbn/inspector-pl import { tracksOverlays } from '@kbn/presentation-containers'; import { EmbeddableApiContext, + getPanelTitle, PublishesPanelTitle, HasParentApi, } from '@kbn/presentation-publishing'; @@ -56,7 +57,7 @@ export class InspectPanelAction implements Action { } const panelTitle = - embeddable.panelTitle?.value ?? + getPanelTitle(embeddable) || i18n.translate('presentationPanel.action.inspectPanel.untitledEmbeddableFilename', { defaultMessage: 'untitled', }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts index 65b47346e50aa..850476b8c9429 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts @@ -12,7 +12,8 @@ import { type PresentationContainer, } from '@kbn/presentation-containers'; import { - PublishesPanelTitle, + getPanelTitle, + type PublishesPanelTitle, type HasUniqueId, type HasParentApi, } from '@kbn/presentation-publishing'; @@ -63,7 +64,7 @@ export const createDrilldownTemplatesFromSiblings = ( id: event.eventId, name: event.action.name, icon: 'dashboardApp', - description: child.panelTitle?.value ?? child.uuid ?? '', + description: getPanelTitle(child) ?? child.uuid ?? '', config: event.action.config, factoryId: event.action.factoryId, triggers: event.triggers, diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts index 8324b85905e9c..d50df1f79f281 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts @@ -18,6 +18,7 @@ import type { PublishesLocalUnifiedSearch, PublishesDataViews, } from '@kbn/presentation-publishing'; +import { getPanelTitle } from '@kbn/presentation-publishing'; import type { UrlTemplateEditorVariable } from '@kbn/kibana-react-plugin/public'; import { txtValue } from './i18n'; import { deleteUndefinedKeys } from './util'; @@ -78,7 +79,7 @@ export const getContextScopeValues = (context: Partial): C return { panel: deleteUndefinedKeys({ id: api.uuid, - title: api.panelTitle?.value ?? api.defaultPanelTitle?.value, + title: getPanelTitle(api), savedObjectId: api.savedObjectId?.value, query: api.parentApi?.localQuery?.value, timeRange: api.localTimeRange?.value ?? api.parentApi?.localTimeRange?.value, diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/event_variables.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/event_variables.ts index a01e423c0d5b5..a66f1d1905dd8 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/event_variables.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/event_variables.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { monaco } from '@kbn/monaco'; -import type { PublishesPanelTitle } from '@kbn/presentation-publishing'; +import { getPanelTitle, type PublishesPanelTitle } from '@kbn/presentation-publishing'; import { ChartActionContext, isRangeSelectTriggerContext, @@ -103,9 +103,10 @@ const getEventScopeFromRowClickTriggerContext = ( for (const columnId of columns) { const column = data.table.columns.find(({ id }) => id === columnId); if (!column) { - // This should never happe, but in case it does we log data necessary for debugging. + // This should never happen, but in case it does we log data necessary for debugging. + const title = getPanelTitle(api); // eslint-disable-next-line no-console - console.error(data, api?.panelTitle ? `Embeddable [${api.panelTitle.value}]` : null); + console.error(data, title ? `Embeddable [${title}]` : null); throw new Error('Could not find a datatable column.'); } values.push(row[columnId]);