string
| |
| [originatingApp](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingapp.md) | string
| |
+| [originatingPath](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md) | string
| |
| [searchSessionId](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.searchsessionid.md) | string
| Pass current search session id when navigating to an editor, Editors could use it continue previous search session |
| [valueInput](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.valueinput.md) | EmbeddableInput
| |
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md
new file mode 100644
index 0000000000000..e255f11f8a059
--- /dev/null
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableEditorState](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) > [originatingPath](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md)
+
+## EmbeddableEditorState.originatingPath property
+
+Signature:
+
+```typescript
+originatingPath?: string;
+```
diff --git a/package.json b/package.json
index 00fa0807e0f93..205685cd5389c 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"dependencies": {
"@elastic/apm-rum": "^5.8.0",
"@elastic/apm-rum-react": "^1.2.11",
- "@elastic/charts": "33.2.2",
+ "@elastic/charts": "34.0.0",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.17",
"@elastic/ems-client": "7.15.0",
@@ -379,6 +379,7 @@
"redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
"redux-thunks": "^1.0.0",
+ "remark-stringify": "^9.0.0",
"regenerator-runtime": "^0.13.3",
"request": "^2.88.0",
"require-in-the-middle": "^5.0.2",
diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts
index 98cf6e70284cd..74ee31ba71104 100644
--- a/src/plugins/embeddable/public/lib/state_transfer/types.ts
+++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts
@@ -17,6 +17,7 @@ export const EMBEDDABLE_EDITOR_STATE_KEY = 'embeddable_editor_state';
*/
export interface EmbeddableEditorState {
originatingApp: string;
+ originatingPath?: string;
embeddableId?: string;
valueInput?: EmbeddableInput;
diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md
index 2e46cb82dc592..3dfe10445fb85 100644
--- a/src/plugins/embeddable/public/public.api.md
+++ b/src/plugins/embeddable/public/public.api.md
@@ -369,6 +369,8 @@ export interface EmbeddableEditorState {
embeddableId?: string;
// (undocumented)
originatingApp: string;
+ // (undocumented)
+ originatingPath?: string;
searchSessionId?: string;
// (undocumented)
valueInput?: EmbeddableInput;
diff --git a/src/plugins/saved_objects/public/finder/index.ts b/src/plugins/saved_objects/public/finder/index.ts
index edec012d90d6f..de6a54795fce5 100644
--- a/src/plugins/saved_objects/public/finder/index.ts
+++ b/src/plugins/saved_objects/public/finder/index.ts
@@ -9,5 +9,6 @@
export {
SavedObjectMetaData,
SavedObjectFinderUi,
+ SavedObjectFinderUiProps,
getSavedObjectFinder,
} from './saved_object_finder';
diff --git a/src/plugins/saved_objects/public/index.ts b/src/plugins/saved_objects/public/index.ts
index 84c39168d82c2..bc84298a63717 100644
--- a/src/plugins/saved_objects/public/index.ts
+++ b/src/plugins/saved_objects/public/index.ts
@@ -17,7 +17,12 @@ export {
SaveResult,
showSaveModal,
} from './save_modal';
-export { getSavedObjectFinder, SavedObjectFinderUi, SavedObjectMetaData } from './finder';
+export {
+ getSavedObjectFinder,
+ SavedObjectFinderUi,
+ SavedObjectFinderUiProps,
+ SavedObjectMetaData,
+} from './finder';
export {
SavedObjectLoader,
SavedObjectLoaderFindOptions,
diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx
index 58c932c3ca164..55f2b4ccd71e9 100644
--- a/x-pack/examples/embedded_lens_example/public/app.tsx
+++ b/x-pack/examples/embedded_lens_example/public/app.tsx
@@ -173,7 +173,9 @@ export const App = (props: {
timeRange: time,
attributes: getLensAttributes(props.defaultIndexPattern!, color),
},
- true
+ {
+ openInNewTab: true,
+ }
);
// eslint-disable-next-line no-bitwise
const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16);
@@ -195,7 +197,9 @@ export const App = (props: {
timeRange: time,
attributes: getLensAttributes(props.defaultIndexPattern!, color),
},
- false
+ {
+ openInNewTab: false,
+ }
);
}}
>
diff --git a/x-pack/plugins/apm/common/agent_name.ts b/x-pack/plugins/apm/common/agent_name.ts
index 650e72751749e..b41ae949d5867 100644
--- a/x-pack/plugins/apm/common/agent_name.ts
+++ b/x-pack/plugins/apm/common/agent_name.ts
@@ -86,3 +86,7 @@ export function isIosAgentName(agentName?: string) {
const lowercased = agentName && agentName.toLowerCase();
return lowercased === 'ios/swift' || lowercased === 'opentelemetry/swift';
}
+
+export function isJRubyAgent(agentName?: string, runtimeName?: string) {
+ return agentName === 'ruby' && runtimeName?.toLowerCase() === 'jruby';
+}
diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx
index c7dd0f46cfc22..8d7d14191a851 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx
@@ -16,7 +16,7 @@ import { UrlParamsProvider } from '../../../context/url_params_context/url_param
import { IUrlParams } from '../../../context/url_params_context/types';
import * as useFetcherHook from '../../../hooks/use_fetcher';
import * as useServiceTransactionTypesHook from '../../../context/apm_service/use_service_transaction_types_fetcher';
-import * as useServiceAgentNameHook from '../../../context/apm_service/use_service_agent_name_fetcher';
+import * as useServiceAgentNameHook from '../../../context/apm_service/use_service_agent_fetcher';
import {
disableConsoleWarning,
renderWithTheme,
@@ -52,9 +52,10 @@ function setup({
// mock agent
jest
- .spyOn(useServiceAgentNameHook, 'useServiceAgentNameFetcher')
+ .spyOn(useServiceAgentNameHook, 'useServiceAgentFetcher')
.mockReturnValue({
agentName: 'nodejs',
+ runtimeName: 'node',
error: undefined,
status: useFetcherHook.FETCH_STATUS.SUCCESS,
});
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx
new file mode 100644
index 0000000000000..5a481b2d6f10c
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { isMetricsTabHidden, isJVMsTabHidden } from './';
+
+describe('APM service template', () => {
+ describe('isMetricsTabHidden', () => {
+ describe('hides metrics tab', () => {
+ [
+ { agentName: undefined },
+ { agentName: 'js-base' },
+ { agentName: 'rum-js' },
+ { agentName: 'opentelemetry/webjs' },
+ { agentName: 'java' },
+ { agentName: 'opentelemetry/java' },
+ { agentName: 'ios/swift' },
+ { agentName: 'opentelemetry/swift' },
+ { agentName: 'ruby', runtimeName: 'jruby' },
+ ].map((input) => {
+ it(`when input ${JSON.stringify(input)}`, () => {
+ expect(isMetricsTabHidden(input)).toBeTruthy();
+ });
+ });
+ });
+ describe('shows metrics tab', () => {
+ [
+ { agentName: 'ruby', runtimeName: 'ruby' },
+ { agentName: 'ruby' },
+ { agentName: 'dotnet' },
+ { agentName: 'go' },
+ { agentName: 'nodejs' },
+ { agentName: 'php' },
+ { agentName: 'python' },
+ ].map((input) => {
+ it(`when input ${JSON.stringify(input)}`, () => {
+ expect(isMetricsTabHidden(input)).toBeFalsy();
+ });
+ });
+ });
+ });
+ describe('isJVMsTabHidden', () => {
+ describe('hides JVMs tab', () => {
+ [
+ { agentName: undefined },
+ { agentName: 'ruby', runtimeName: 'ruby' },
+ { agentName: 'ruby' },
+ { agentName: 'dotnet' },
+ { agentName: 'go' },
+ { agentName: 'nodejs' },
+ { agentName: 'php' },
+ { agentName: 'python' },
+ ].map((input) => {
+ it(`when input ${JSON.stringify(input)}`, () => {
+ expect(isJVMsTabHidden(input)).toBeTruthy();
+ });
+ });
+ });
+ describe('shows JVMs tab', () => {
+ [
+ { agentName: 'java' },
+ { agentName: 'opentelemetry/java' },
+ { agentName: 'ruby', runtimeName: 'jruby' },
+ ].map((input) => {
+ it(`when input ${JSON.stringify(input)}`, () => {
+ expect(isJVMsTabHidden(input)).toBeFalsy();
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
index efd91fa506c10..c12fdab09613c 100644
--- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
@@ -18,6 +18,7 @@ import React from 'react';
import {
isIosAgentName,
isJavaAgentName,
+ isJRubyAgent,
isRumAgentName,
} from '../../../../../common/agent_name';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
@@ -118,8 +119,34 @@ function TemplateWithContext({
);
}
+export function isMetricsTabHidden({
+ agentName,
+ runtimeName,
+}: {
+ agentName?: string;
+ runtimeName?: string;
+}) {
+ return (
+ !agentName ||
+ isRumAgentName(agentName) ||
+ isJavaAgentName(agentName) ||
+ isIosAgentName(agentName) ||
+ isJRubyAgent(agentName, runtimeName)
+ );
+}
+
+export function isJVMsTabHidden({
+ agentName,
+ runtimeName,
+}: {
+ agentName?: string;
+ runtimeName?: string;
+}) {
+ return !(isJavaAgentName(agentName) || isJRubyAgent(agentName, runtimeName));
+}
+
function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
- const { agentName } = useApmServiceContext();
+ const { agentName, runtimeName } = useApmServiceContext();
const { config } = useApmPluginContext();
const router = useApmRouter();
@@ -189,11 +216,7 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
label: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', {
defaultMessage: 'Metrics',
}),
- hidden:
- !agentName ||
- isRumAgentName(agentName) ||
- isJavaAgentName(agentName) ||
- isIosAgentName(agentName),
+ hidden: isMetricsTabHidden({ agentName, runtimeName }),
},
{
key: 'nodes',
@@ -204,7 +227,7 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
label: i18n.translate('xpack.apm.serviceDetails.nodesTabLabel', {
defaultMessage: 'JVMs',
}),
- hidden: !isJavaAgentName(agentName),
+ hidden: isJVMsTabHidden({ agentName, runtimeName }),
},
{
key: 'service-map',
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx
index 51250818a2269..3a4e206957a00 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx
@@ -22,6 +22,7 @@ import {
RecursivePartial,
ScaleType,
Settings,
+ LineAnnotationStyle,
} from '@elastic/charts';
import euiVars from '@elastic/eui/dist/eui_theme_light.json';
@@ -91,7 +92,7 @@ interface CorrelationsChartProps {
selection?: [number, number];
}
-const getAnnotationsStyle = (color = 'gray') => ({
+const getAnnotationsStyle = (color = 'gray'): LineAnnotationStyle => ({
line: {
strokeWidth: 1,
stroke: color,
diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx
index 9a66441b8e98b..33bbe390e3c6f 100644
--- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx
@@ -13,7 +13,7 @@ import {
TRANSACTION_REQUEST,
} from '../../../common/transaction_types';
import { useServiceTransactionTypesFetcher } from './use_service_transaction_types_fetcher';
-import { useServiceAgentNameFetcher } from './use_service_agent_name_fetcher';
+import { useServiceAgentFetcher } from './use_service_agent_fetcher';
import { APIReturnType } from '../../services/rest/createCallApmApi';
import { useServiceAlertsFetcher } from './use_service_alerts_fetcher';
import { useApmParams } from '../../hooks/use_apm_params';
@@ -28,6 +28,7 @@ export const APMServiceContext = createContext<{
transactionType?: string;
transactionTypes: string[];
alerts: APMServiceAlert[];
+ runtimeName?: string;
}>({ serviceName: '', transactionTypes: [], alerts: [] });
export function ApmServiceContextProvider({
@@ -40,7 +41,7 @@ export function ApmServiceContextProvider({
query,
} = useApmParams('/services/:serviceName');
- const { agentName } = useServiceAgentNameFetcher(serviceName);
+ const { agentName, runtimeName } = useServiceAgentFetcher(serviceName);
const transactionTypes = useServiceTransactionTypesFetcher(serviceName);
@@ -65,6 +66,7 @@ export function ApmServiceContextProvider({
transactionType,
transactionTypes,
alerts,
+ runtimeName,
}}
children={children}
/>
diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts
similarity index 70%
rename from x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts
rename to x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts
index 82198eb73b3cb..214b72a34d6e5 100644
--- a/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts
+++ b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts
@@ -8,14 +8,19 @@
import { useFetcher } from '../../hooks/use_fetcher';
import { useUrlParams } from '../url_params_context/use_url_params';
-export function useServiceAgentNameFetcher(serviceName?: string) {
+const INITIAL_STATE = {
+ agentName: undefined,
+ runtimeName: undefined,
+};
+
+export function useServiceAgentFetcher(serviceName?: string) {
const { urlParams } = useUrlParams();
const { start, end } = urlParams;
- const { data, error, status } = useFetcher(
+ const { data = INITIAL_STATE, error, status } = useFetcher(
(callApmApi) => {
if (serviceName && start && end) {
return callApmApi({
- endpoint: 'GET /api/apm/services/{serviceName}/agent_name',
+ endpoint: 'GET /api/apm/services/{serviceName}/agent',
params: {
path: { serviceName },
query: { start, end },
@@ -26,5 +31,5 @@ export function useServiceAgentNameFetcher(serviceName?: string) {
[serviceName, start, end]
);
- return { agentName: data?.agentName, status, error };
+ return { ...data, status, error };
}
diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
index be664529abab4..1b5df64dd8d00 100644
--- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
@@ -63,14 +63,10 @@ Object {
],
},
"body": Object {
- "aggs": Object {
- "agents": Object {
- "terms": Object {
- "field": "agent.name",
- "size": 1,
- },
- },
- },
+ "_source": Array [
+ "service.runtime.name",
+ "agent.name",
+ ],
"query": Object {
"bool": Object {
"filter": Array [
@@ -88,10 +84,20 @@ Object {
},
},
},
+ Object {
+ "exists": Object {
+ "field": "service.runtime.name",
+ },
+ },
+ Object {
+ "exists": Object {
+ "field": "agent.name",
+ },
+ },
],
},
},
- "size": 0,
+ "size": 1,
},
"terminateAfter": 1,
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/plugins/apm/server/lib/services/get_service_agent.ts
similarity index 64%
rename from x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
rename to x-pack/plugins/apm/server/lib/services/get_service_agent.ts
index 49489f2b33888..2a6ec74bc0d1a 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_agent.ts
@@ -9,12 +9,24 @@ import { ProcessorEvent } from '../../../common/processor_event';
import {
AGENT_NAME,
SERVICE_NAME,
+ SERVICE_RUNTIME_NAME,
} from '../../../common/elasticsearch_fieldnames';
import { rangeQuery } from '../../../../observability/server';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
-export async function getServiceAgentName({
+interface ServiceAgent {
+ service?: {
+ runtime: {
+ name: string;
+ };
+ };
+ agent?: {
+ name: string;
+ };
+}
+
+export async function getServiceAgent({
serviceName,
setup,
searchAggregatedTransactions,
@@ -37,27 +49,37 @@ export async function getServiceAgentName({
],
},
body: {
- size: 0,
+ size: 1,
+ _source: [SERVICE_RUNTIME_NAME, AGENT_NAME],
query: {
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
...rangeQuery(start, end),
+ {
+ exists: {
+ field: SERVICE_RUNTIME_NAME,
+ },
+ },
+ {
+ exists: {
+ field: AGENT_NAME,
+ },
+ },
],
},
},
- aggs: {
- agents: {
- terms: { field: AGENT_NAME, size: 1 },
- },
- },
},
};
- const { aggregations } = await apmEventClient.search(
+ const response = await apmEventClient.search(
'get_service_agent_name',
params
);
- const agentName = aggregations?.agents.buckets[0]?.key as string | undefined;
- return { agentName };
+ if (response.hits.total.value === 0) {
+ return {};
+ }
+
+ const { service, agent } = response.hits.hits[0]._source as ServiceAgent;
+ return { agentName: agent?.name, runtimeName: service?.runtime.name };
}
diff --git a/x-pack/plugins/apm/server/lib/services/queries.test.ts b/x-pack/plugins/apm/server/lib/services/queries.test.ts
index a34382ddaf1fb..be5f280477a09 100644
--- a/x-pack/plugins/apm/server/lib/services/queries.test.ts
+++ b/x-pack/plugins/apm/server/lib/services/queries.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { getServiceAgentName } from './get_service_agent_name';
+import { getServiceAgent } from './get_service_agent';
import { getServiceTransactionTypes } from './get_service_transaction_types';
import { getServicesItems } from './get_services/get_services_items';
import { getLegacyDataStatus } from './get_services/get_legacy_data_status';
@@ -25,7 +25,7 @@ describe('services queries', () => {
it('fetches the service agent name', async () => {
mock = await inspectSearchParams((setup) =>
- getServiceAgentName({
+ getServiceAgent({
serviceName: 'foo',
setup,
searchAggregatedTransactions: false,
diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts
index b4d185fecf5e2..32a7dcefb5cc8 100644
--- a/x-pack/plugins/apm/server/routes/services.ts
+++ b/x-pack/plugins/apm/server/routes/services.ts
@@ -16,7 +16,7 @@ import { getThroughputUnit } from '../lib/helpers/calculate_throughput';
import { setupRequest } from '../lib/helpers/setup_request';
import { getServiceAnnotations } from '../lib/services/annotations';
import { getServices } from '../lib/services/get_services';
-import { getServiceAgentName } from '../lib/services/get_service_agent_name';
+import { getServiceAgent } from '../lib/services/get_service_agent';
import { getServiceAlerts } from '../lib/services/get_service_alerts';
import { getServiceDependencies } from '../lib/services/get_service_dependencies';
import { getServiceInstanceMetadataDetails } from '../lib/services/get_service_instance_metadata_details';
@@ -164,8 +164,8 @@ const serviceMetadataIconsRoute = createApmServerRoute({
},
});
-const serviceAgentNameRoute = createApmServerRoute({
- endpoint: 'GET /api/apm/services/{serviceName}/agent_name',
+const serviceAgentRoute = createApmServerRoute({
+ endpoint: 'GET /api/apm/services/{serviceName}/agent',
params: t.type({
path: t.type({
serviceName: t.string,
@@ -185,7 +185,7 @@ const serviceAgentNameRoute = createApmServerRoute({
kuery: '',
});
- return getServiceAgentName({
+ return getServiceAgent({
serviceName,
setup,
searchAggregatedTransactions,
@@ -909,7 +909,7 @@ export const serviceRouteRepository = createApmServerRouteRepository()
.add(servicesDetailedStatisticsRoute)
.add(serviceMetadataDetailsRoute)
.add(serviceMetadataIconsRoute)
- .add(serviceAgentNameRoute)
+ .add(serviceAgentRoute)
.add(serviceTransactionTypesRoute)
.add(serviceNodeMetadataRoute)
.add(serviceAnnotationsRoute)
diff --git a/x-pack/plugins/cases/README.md b/x-pack/plugins/cases/README.md
index 25113ccbb30df..f894ca23dfbf0 100644
--- a/x-pack/plugins/cases/README.md
+++ b/x-pack/plugins/cases/README.md
@@ -43,6 +43,16 @@ cases: CasesUiStart;
cases.getCreateCase({
onCancel: handleSetIsCancel,
onSuccess,
+ lensIntegration?: {
+ plugins: {
+ parsingPlugin,
+ processingPluginRenderer,
+ uiPlugin,
+ },
+ hooks: {
+ useInsertTimeline,
+ },
+ }
timelineIntegration?: {
plugins: {
parsingPlugin,
diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts
index 3edbd3443ffc1..bf4ec0da6ee56 100644
--- a/x-pack/plugins/cases/common/ui/types.ts
+++ b/x-pack/plugins/cases/common/ui/types.ts
@@ -18,6 +18,12 @@ import {
UserActionField,
} from '../api';
+export interface CasesUiConfigType {
+ markdownPlugins: {
+ lens: boolean;
+ };
+}
+
export const StatusAll = 'all' as const;
export type StatusAllType = typeof StatusAll;
diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts
new file mode 100644
index 0000000000000..bc67e1b3228bb
--- /dev/null
+++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts
@@ -0,0 +1,8 @@
+/*
+ * 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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const LENS_ID = 'lens';
diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts
new file mode 100644
index 0000000000000..4f48da5838380
--- /dev/null
+++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './constants';
+export * from './parser';
+export * from './serializer';
diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/parser.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/parser.ts
new file mode 100644
index 0000000000000..58ebfd76d5ac5
--- /dev/null
+++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/parser.ts
@@ -0,0 +1,77 @@
+/*
+ * 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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Plugin } from 'unified';
+import { RemarkTokenizer } from '@elastic/eui';
+import { LENS_ID } from './constants';
+
+export const LensParser: Plugin = function () {
+ const Parser = this.Parser;
+ const tokenizers = Parser.prototype.blockTokenizers;
+ const methods = Parser.prototype.blockMethods;
+
+ const tokenizeLens: RemarkTokenizer = function (eat, value, silent) {
+ if (value.startsWith(`!{${LENS_ID}`) === false) return true;
+
+ const nextChar = value[6];
+
+ if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a lens
+
+ if (silent) {
+ return true;
+ }
+
+ // is there a configuration?
+ const hasConfiguration = nextChar === '{';
+
+ let match = `!{${LENS_ID}`;
+ let configuration = {};
+
+ if (hasConfiguration) {
+ let configurationString = '';
+
+ let openObjects = 0;
+
+ for (let i = 6; i < value.length; i++) {
+ const char = value[i];
+ if (char === '{') {
+ openObjects++;
+ configurationString += char;
+ } else if (char === '}') {
+ openObjects--;
+ if (openObjects === -1) {
+ break;
+ }
+ configurationString += char;
+ } else {
+ configurationString += char;
+ }
+ }
+
+ match += configurationString;
+ try {
+ configuration = JSON.parse(configurationString);
+ } catch (e) {
+ const now = eat.now();
+ this.file.fail(`Unable to parse lens JSON configuration: ${e}`, {
+ line: now.line,
+ column: now.column + 6,
+ });
+ }
+ }
+
+ match += '}';
+
+ return eat(match)({
+ type: LENS_ID,
+ ...configuration,
+ });
+ };
+
+ tokenizers.lens = tokenizeLens;
+ methods.splice(methods.indexOf('text'), 0, LENS_ID);
+};
diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts
new file mode 100644
index 0000000000000..e561b2f8cfb8a
--- /dev/null
+++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts
@@ -0,0 +1,20 @@
+/*
+ * 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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { TimeRange } from 'src/plugins/data/common';
+import { LENS_ID } from './constants';
+
+export interface LensSerializerProps {
+ attributes: Record
-