elements for "m"
- const marks = container.querySelectorAll('mark');
- expect(marks.length).toBe(2);
- expect(marks[0].textContent?.toLowerCase()).toBe('m');
- expect(marks[1].textContent?.toLowerCase()).toBe('m');
- });
-
- it('should render text with no highlights if searchTerm does not match', () => {
- const { container, getByText } = render();
- expect(container.querySelector('mark')).not.toBeInTheDocument();
- expect(getByText('Memory')).toBeInTheDocument();
- });
-
- it('handles empty text gracefully', () => {
- const { container } = render();
- expect(container.textContent).toBe('');
- expect(container.querySelector('mark')).not.toBeInTheDocument();
- });
-});
diff --git a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/chart_title.tsx b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/chart_title.tsx
deleted file mode 100644
index 96a4551ca0337..0000000000000
--- a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/chart_title.tsx
+++ /dev/null
@@ -1,78 +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 { useEuiTheme, EuiHighlight, EuiTextTruncate } from '@elastic/eui';
-import { css } from '@emotion/react';
-import { getHighlightColors } from '@kbn/data-grid-in-table-search/src/get_highlight_colors';
-import React, { useMemo } from 'react';
-
-export const ChartTitle = ({
- highlight,
- title,
-}: {
- highlight?: string;
- title: string;
-}): React.ReactNode => {
- const { euiTheme } = useEuiTheme();
- const colors = useMemo(() => getHighlightColors(euiTheme), [euiTheme]);
-
- const { headerStyles, chartTitleCss } = useMemo(() => {
- return {
- headerStyles: css`
- position: absolute;
- width: 100%;
- max-height: ${euiTheme.size.l};
- z-index: ${Number(euiTheme.levels.content) + 1};
- transition: outline-color ${euiTheme.animation.extraFast},
- z-index ${euiTheme.animation.extraFast};
- transition-delay: ${euiTheme.animation.fast};
-
- overflow: hidden;
- height: 100%;
- line-height: ${euiTheme.size.l};
- padding: 0px ${euiTheme.size.s};
-
- pointer-events: none;
- `,
- chartTitleCss: css`
- font-weight: ${euiTheme.font.weight.bold};
- `,
- };
- }, [
- euiTheme.size.l,
- euiTheme.size.s,
- euiTheme.levels.content,
- euiTheme.animation.extraFast,
- euiTheme.animation.fast,
- euiTheme.font.weight.bold,
- ]);
-
- return (
-
-
- {highlight ? (
-
- {title}
-
- ) : (
-
- )}
-
-
- );
-};
diff --git a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/index.tsx b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/index.tsx
index 9dd48dfdad43b..afaed63abdded 100644
--- a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/index.tsx
+++ b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/index.tsx
@@ -81,13 +81,6 @@ export const Chart = ({
height: ${ChartSizes[size]}px;
outline: ${euiTheme.border.width.thin} solid ${euiTheme.colors.lightShade};
border-radius: ${euiTheme.border.radius.medium};
-
- &:hover {
- .metricsExperienceChartTitle {
- z-index: ${Number(euiTheme.levels.menu) + 1};
- transition: none;
- }
- }
`}
ref={chartRef}
>
diff --git a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.test.tsx b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.test.tsx
new file mode 100644
index 0000000000000..cb32597da830e
--- /dev/null
+++ b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.test.tsx
@@ -0,0 +1,199 @@
+/*
+ * 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 React from 'react';
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
+import { EuiThemeProvider } from '@elastic/eui';
+import { LensWrapper } from './lens_wrapper';
+import type { LensWrapperProps } from './lens_wrapper';
+
+// Mock the EmbeddableComponent
+const mockEmbeddableComponent = jest.fn((props) => (
+
+ Mock EmbeddableComponent
+
+));
+
+// Mock useLensExtraActions
+jest.mock('./hooks/use_lens_extra_actions', () => ({
+ useLensExtraActions: jest.fn(() => []),
+}));
+
+describe('LensWrapper', () => {
+ const mockLensProps = {
+ attributes: {
+ title: 'Test Chart',
+ visualizationType: 'bar',
+ state: {
+ datasourceStates: {},
+ visualization: {},
+ query: { query: '', language: 'kuery' },
+ filters: [],
+ },
+ references: [],
+ },
+ timeRange: {
+ from: 'now-15m',
+ to: 'now',
+ },
+ };
+
+ const mockServices = {
+ lens: {
+ EmbeddableComponent: mockEmbeddableComponent,
+ },
+ };
+
+ const defaultProps: LensWrapperProps = {
+ lensProps: mockLensProps,
+ services: mockServices as any,
+ abortController: undefined,
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('titleHighlight prop', () => {
+ it('passes titleHighlight prop to EmbeddableComponent', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(mockEmbeddableComponent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ titleHighlight: 'cpu',
+ }),
+ expect.anything()
+ );
+
+ const embeddableElement = getByTestId('embeddable-component');
+ expect(embeddableElement).toHaveAttribute('data-title-highlight', 'cpu');
+ });
+
+ it('passes titleHighlight when undefined', () => {
+ render(
+
+
+
+ );
+
+ expect(mockEmbeddableComponent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ titleHighlight: undefined,
+ }),
+ expect.anything()
+ );
+ });
+
+ it('passes titleHighlight along with other props to EmbeddableComponent', () => {
+ render(
+
+
+
+ );
+
+ expect(mockEmbeddableComponent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ titleHighlight: 'memory',
+ syncTooltips: true,
+ syncCursor: true,
+ title: mockLensProps.attributes.title,
+ withDefaultActions: true,
+ }),
+ expect.anything()
+ );
+ });
+ });
+
+ describe('header visibility', () => {
+ it('header remains visible when titleHighlight is provided', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ // Verify that the embeddable component is rendered (header will be rendered by EmbeddableComponent)
+ expect(getByTestId('embeddable-component')).toBeInTheDocument();
+ });
+
+ it('does not hide .embPanel__header with CSS', () => {
+ const { container } = render(
+
+
+
+ );
+
+ // Verify no CSS rules hiding the header
+ const style = container.querySelector('style');
+ if (style) {
+ expect(style.textContent).not.toContain('.embPanel__header');
+ expect(style.textContent).not.toContain('visibility: hidden');
+ }
+ });
+ });
+
+ describe('integration with EmbeddableComponent', () => {
+ it('passes all required props to EmbeddableComponent', () => {
+ const onBrushEnd = jest.fn();
+ const onFilter = jest.fn();
+ const abortController = new AbortController();
+
+ render(
+
+
+
+ );
+
+ expect(mockEmbeddableComponent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ titleHighlight: 'test',
+ title: mockLensProps.attributes.title,
+ ...mockLensProps,
+ onBrushEnd,
+ onFilter,
+ abortController,
+ withDefaultActions: true,
+ disabledActions: expect.arrayContaining([
+ 'ACTION_CUSTOMIZE_PANEL',
+ 'ACTION_EXPORT_CSV',
+ 'alertRule',
+ ]),
+ }),
+ expect.anything()
+ );
+ });
+
+ it('wraps EmbeddableComponent in PresentationPanelQuickActionContext', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ // The component should be wrapped in the context provider
+ expect(getByTestId('embeddable-component')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.tsx b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.tsx
index 4ba9162984d70..243517e78ae91 100644
--- a/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.tsx
+++ b/src/platform/packages/shared/kbn-unified-metrics-grid/src/components/chart/lens_wrapper.tsx
@@ -13,7 +13,6 @@ import { PresentationPanelQuickActionContext } from '@kbn/presentation-panel-plu
import type { LensProps } from './hooks/use_lens_props';
import { useLensExtraActions } from './hooks/use_lens_extra_actions';
import { ACTION_EXPLORE_IN_DISCOVER_TAB } from '../../common/constants';
-import { ChartTitle } from './chart_title';
import type { UnifiedMetricsGridProps } from '../../types';
export type LensWrapperProps = {
@@ -58,10 +57,6 @@ export function LensWrapper({
width: 100%;
}
- & .embPanel__header {
- visibility: hidden;
- }
-
& .lnsExpressionRenderer {
width: 100%;
margin: auto;
@@ -117,10 +112,10 @@ export function LensWrapper({
-
void;
-} & Pick;
+} & Pick<
+ PresentationPanelInternalProps,
+ 'showBadges' | 'getActions' | 'showNotifications' | 'titleHighlight'
+>;
export const PresentationPanelHeader = <
ApiType extends DefaultPresentationPanelApi = DefaultPresentationPanelApi
@@ -38,6 +41,7 @@ export const PresentationPanelHeader = <
setDragHandle,
showBadges = true,
showNotifications = true,
+ titleHighlight,
}: PresentationPanelHeaderProps) => {
const { euiTheme } = useEuiTheme();
@@ -108,6 +112,7 @@ export const PresentationPanelHeader = <
hideTitle={hideTitle}
panelTitle={panelTitle}
panelDescription={panelDescription}
+ titleHighlight={titleHighlight}
/>
{showBadges && badgeElements}
diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx
new file mode 100644
index 0000000000000..ea7f2881d08ea
--- /dev/null
+++ b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 React from 'react';
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import { EuiThemeProvider } from '@elastic/eui';
+import { BehaviorSubject } from 'rxjs';
+import { PresentationPanelTitle } from './presentation_panel_title';
+import type { DefaultPresentationPanelApi } from '../types';
+
+describe('PresentationPanelTitle', () => {
+ const mockApi: DefaultPresentationPanelApi = {
+ uuid: 'test',
+ title$: new BehaviorSubject('CPU Usage'),
+ };
+
+ const defaultProps = {
+ api: mockApi,
+ headerId: 'test-header-id',
+ };
+
+ const renderWithTheme = (component: React.ReactElement) => {
+ return render({component});
+ };
+
+ describe('titleHighlight functionality', () => {
+ it('renders plain text when titleHighlight is not provided', () => {
+ const { container } = renderWithTheme(
+
+ );
+ const titleElement = screen.getByTestId('embeddablePanelTitle');
+ expect(titleElement).toHaveTextContent('CPU Usage');
+ expect(container.querySelector('mark')).not.toBeInTheDocument();
+ });
+
+ it('renders EuiHighlight component when titleHighlight is provided', () => {
+ const { container } = renderWithTheme(
+
+ );
+ const mark = container.querySelector('mark');
+ expect(mark).toBeInTheDocument();
+ expect(mark?.textContent?.toLowerCase()).toBe('cpu');
+ });
+ });
+});
diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx
index 10912e388de74..025ccb07606a5 100644
--- a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx
+++ b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx
@@ -8,6 +8,7 @@
*/
import {
+ EuiHighlight,
EuiIcon,
EuiLink,
EuiScreenReaderOnly,
@@ -31,6 +32,7 @@ export const PresentationPanelTitle = ({
hideTitle,
panelTitle,
panelDescription,
+ titleHighlight,
}: {
api: unknown;
headerId: string;
@@ -38,6 +40,7 @@ export const PresentationPanelTitle = ({
panelTitle?: string;
panelDescription?: string;
viewMode?: ViewMode;
+ titleHighlight?: string;
}) => {
const { euiTheme } = useEuiTheme();
@@ -60,10 +63,19 @@ export const PresentationPanelTitle = ({
}
`;
+ const titleContent =
+ titleHighlight && panelTitle ? (
+
+ {panelTitle ?? ''}
+
+ ) : (
+ panelTitle
+ );
+
if (viewMode !== 'edit' || !isApiCompatibleWithCustomizePanelAction(api)) {
return (
- {panelTitle}
+ {titleContent}
);
}
@@ -79,10 +91,10 @@ export const PresentationPanelTitle = ({
})}
data-test-subj="embeddablePanelTitle"
>
- {panelTitle}
+ {titleContent}
);
- }, [onClick, hideTitle, panelTitle, viewMode, api, euiTheme]);
+ }, [onClick, hideTitle, panelTitle, viewMode, api, euiTheme, titleHighlight]);
const describedPanelTitleElement = useMemo(() => {
if (hideTitle) return null;
diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.test.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.test.tsx
index 783aa1b1e816e..3c493e2810eb6 100644
--- a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.test.tsx
+++ b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.test.tsx
@@ -7,6 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
+import { EuiThemeProvider } from '@elastic/eui';
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
import type { DataView } from '@kbn/data-views-plugin/common';
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
@@ -330,5 +331,27 @@ describe('Presentation panel', () => {
await renderPresentationPanel({ api });
expect(screen.queryByTestId('presentationPanelTitle')).not.toBeInTheDocument();
});
+
+ it('passes titleHighlight prop through to PresentationPanelHeader', async () => {
+ const api: DefaultPresentationPanelApi = {
+ uuid: 'test',
+ title$: new BehaviorSubject('CPU Usage'),
+ };
+ const { container } = render(
+
+
+
+ );
+ await waitFor(() => {
+ expect(screen.getByTestId('embeddablePanelTitle')).toBeInTheDocument();
+ });
+ await waitFor(() => {
+ const mark = container.querySelector('mark');
+ expect(mark).toBeInTheDocument();
+ });
+ });
});
});
diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx
index db9a769fa38e9..e09c2fbf13cef 100644
--- a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx
+++ b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx
@@ -37,6 +37,7 @@ export const PresentationPanelInternal = <
showNotifications,
getActions,
actionPredicate,
+ titleHighlight,
Component,
componentProps,
@@ -143,6 +144,7 @@ export const PresentationPanelInternal = <
showNotifications={showNotifications}
panelTitle={panelTitle ?? defaultPanelTitle}
panelDescription={panelDescription ?? defaultPanelDescription}
+ titleHighlight={titleHighlight}
/>
)}
{blockingError && api && (
diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/types.ts b/src/platform/plugins/private/presentation_panel/public/panel_component/types.ts
index d0c40a06f6041..80b12472f2d02 100644
--- a/src/platform/plugins/private/presentation_panel/public/panel_component/types.ts
+++ b/src/platform/plugins/private/presentation_panel/public/panel_component/types.ts
@@ -65,6 +65,11 @@ export interface PresentationPanelInternalProps<
* logic, then this could be removed.
*/
setDragHandles?: (refs: Array) => void;
+
+ /**
+ * Optional search term to highlight in the panel title
+ */
+ titleHighlight?: string;
}
/**
diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx
index 5717baaaf759f..409a1c1b6367d 100644
--- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx
+++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx
@@ -42,6 +42,7 @@ type PanelProps = Pick<
| 'hideHeader'
| 'hideInspector'
| 'getActions'
+ | 'titleHighlight'
>;
/**
@@ -67,6 +68,7 @@ export function LensRenderer({
forceDSL,
hidePanelTitles,
lastReloadRequestTime,
+ titleHighlight,
...props
}: LensRendererProps) {
// Use the settings interface to store panel settings
@@ -131,6 +133,7 @@ export function LensRenderer({
showNotifications: false,
showShadow: false,
showBadges: false,
+ titleHighlight,
getActions: async (triggerId, context) => {
const actions = withDefaultActions
? await lensApi?.getTriggerCompatibleActions(triggerId, context)
@@ -139,7 +142,7 @@ export function LensRenderer({
return (extraActions ?? []).concat(actions || []);
},
};
- }, [showInspector, withDefaultActions, extraActions, lensApi]);
+ }, [showInspector, withDefaultActions, extraActions, lensApi, titleHighlight]);
return (