From 9523a78e7a4a7fc924e4304f4d8ec07b6b1f7692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 20 Nov 2023 14:15:13 +0100 Subject: [PATCH] [APM] Make data view space aware (#170857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/169924 Related SDH: https://github.com/elastic/sdh-apm/issues/1080 (_internal_) The APM data view is automatically created when opening the APM UI app via a request to `POST /internal/apm/data_view/static`. This creates a data view which is made available to all spaces. The problem with this approach is that users can change index settings (apm index patterns) per space, which means that there should be a data view per space as well. This PR ensures that a data view is created per space and that any links to the data view uses the spaace specific dataview --------- Co-authored-by: Caue Marcondes Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com> --- .../plugins/apm/common/data_view_constants.ts | 10 +- .../app/diagnostics/apm_documents_tab.tsx | 51 +++++++-- .../app/metrics/static_dashboard/helper.ts | 6 +- .../app/metrics/static_dashboard/index.tsx | 13 +-- .../service_overview/geo_map/embedded_map.tsx | 6 +- .../get_http_requests_map_layer_list.ts | 10 +- .../geo_map/map_layers/get_layer_list.ts | 12 ++- .../map_layers/get_session_map_layer_list.ts | 10 +- .../links/discover_links/discover_link.tsx | 8 +- .../discover_links.integration.test.tsx | 8 +- .../transaction_action_menu/sections.test.ts | 9 +- .../transaction_action_menu/sections.ts | 3 + .../transaction_action_menu.tsx | 3 + .../apm/public/hooks/use_apm_data_view.ts | 10 +- .../apm/public/hooks/use_data_view_id.tsx | 29 +++++ x-pack/plugins/apm/server/plugin.ts | 22 ---- .../data_view/create_static_data_view.test.ts | 26 ++++- .../data_view/create_static_data_view.ts | 101 +++++++++--------- ...> get_apm_data_view_index_pattern.test.ts} | 12 +-- ....ts => get_apm_data_view_index_pattern.ts} | 2 +- .../apm/server/routes/data_view/route.ts | 26 +++-- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../app/rum_dashboard/ux_overview_fetchers.ts | 13 ++- .../ux/public/hooks/use_dynamic_data_view.ts | 4 +- .../common/apm_api_supertest.ts | 4 +- .../tests/data_view/static.spec.ts | 85 +++++++++------ 28 files changed, 303 insertions(+), 183 deletions(-) create mode 100644 x-pack/plugins/apm/public/hooks/use_data_view_id.tsx rename x-pack/plugins/apm/server/routes/data_view/{get_apm_data_view_title.test.ts => get_apm_data_view_index_pattern.test.ts} (69%) rename x-pack/plugins/apm/server/routes/data_view/{get_apm_data_view_title.ts => get_apm_data_view_index_pattern.ts} (87%) diff --git a/x-pack/plugins/apm/common/data_view_constants.ts b/x-pack/plugins/apm/common/data_view_constants.ts index b448918f8facf..c50966399ad22 100644 --- a/x-pack/plugins/apm/common/data_view_constants.ts +++ b/x-pack/plugins/apm/common/data_view_constants.ts @@ -5,5 +5,11 @@ * 2.0. */ -// value of const needs to be backwards compatible -export const APM_STATIC_DATA_VIEW_ID = 'apm_static_index_pattern_id'; +export const DO_NOT_USE_LEGACY_APM_STATIC_DATA_VIEW_ID = + 'apm_static_index_pattern_id'; + +const APM_STATIC_DATA_VIEW_ID_PREFIX = 'apm_static_data_view_id'; + +export function getDataViewId(spaceId: string) { + return `${APM_STATIC_DATA_VIEW_ID_PREFIX}_${spaceId}`; +} diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx index d4776c8995c43..03fcd2610f23b 100644 --- a/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx +++ b/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx @@ -13,20 +13,23 @@ import { EuiText, EuiToolTip, } from '@elastic/eui'; -import React, { useState, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { orderBy } from 'lodash'; -import { useApmParams } from '../../../hooks/use_apm_params'; +import React, { useMemo, useState } from 'react'; import { asBigNumber, asInteger } from '../../../../common/utils/formatters'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../../common/data_view_constants'; import type { ApmEvent } from '../../../../server/routes/diagnostics/bundle/get_apm_events'; -import { useDiagnosticsContext } from './context/use_diagnostics'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useDataViewId } from '../../../hooks/use_data_view_id'; import { ApmPluginStartDeps } from '../../../plugin'; import { SearchBar } from '../../shared/search_bar/search_bar'; +import { useDiagnosticsContext } from './context/use_diagnostics'; export function DiagnosticsApmDocuments() { const { diagnosticsBundle, isImported } = useDiagnosticsContext(); const { discover } = useKibana().services; + const dataViewId = useDataViewId(); + const [sortField, setSortField] = useState('name'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); const { @@ -103,7 +106,7 @@ export function DiagnosticsApmDocuments() { language: 'kuery', query: item.kuery, }, - dataViewId: APM_STATIC_DATA_VIEW_ID, + dataViewId, timeRange: rangeTo && rangeFrom ? { @@ -123,13 +126,37 @@ export function DiagnosticsApmDocuments() { {isImported && diagnosticsBundle ? ( <> - From: {new Date(diagnosticsBundle.params.start).toISOString()} + {i18n.translate( + 'xpack.apm.diagnosticsApmDocuments.from:BadgeLabel', + { + defaultMessage: 'From: {date}', + values: { + date: new Date(diagnosticsBundle.params.start).toISOString(), + }, + } + )} - To: {new Date(diagnosticsBundle.params.end).toISOString()} + {i18n.translate('xpack.apm.diagnosticsApmDocuments.to:BadgeLabel', { + defaultMessage: 'To: {date}', + values: { + date: new Date(diagnosticsBundle.params.end).toISOString(), + }, + })} - Filter: {diagnosticsBundle?.params.kuery ?? Empty} + {i18n.translate( + 'xpack.apm.diagnosticsApmDocuments.filter:BadgeLabel', + { defaultMessage: 'Filter:' } + )} + {diagnosticsBundle?.params.kuery ?? ( + + {i18n.translate( + 'xpack.apm.diagnosticsApmDocuments.em.emptyLabel', + { defaultMessage: 'Empty' } + )} + + )} @@ -183,7 +210,13 @@ function IntervalDocCount({ - ({asBigNumber(interval.eventDocCount)} events) + {i18n.translate('xpack.apm.intervalDocCount.TextLabel', { + defaultMessage: + '({docCount} {docCount, plural, one {event} other {events}})', + values: { + docCount: asBigNumber(interval.eventDocCount), + }, + })} diff --git a/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts b/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts index a6d22ea7b6a55..780e4387b0e10 100644 --- a/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts +++ b/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts @@ -6,7 +6,6 @@ */ import type { DashboardPanelMap } from '@kbn/dashboard-plugin/common'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../../../common/data_view_constants'; import { AGENT_NAME_DASHBOARD_FILE_MAPPING, loadDashboardFile, @@ -29,7 +28,8 @@ function getDashboardFile({ agentName }: MetricsDashboardProps) { } export async function getDashboardPanelMap( - props: MetricsDashboardProps + props: MetricsDashboardProps, + dataViewId: string ): Promise { const dashboardFile = getDashboardFile(props); const panelsRawObj = !!dashboardFile @@ -42,7 +42,7 @@ export async function getDashboardPanelMap( const panelsStr: string = ( panelsRawObj.attributes.panelsJSON as string - ).replaceAll('APM_STATIC_DATA_VIEW_ID', APM_STATIC_DATA_VIEW_ID); + ).replaceAll('APM_STATIC_DATA_VIEW_ID', dataViewId); const panelsRawObjects = JSON.parse(panelsStr) as any[]; diff --git a/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/index.tsx b/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/index.tsx index 50bda742eb377..29ebf34cca44f 100644 --- a/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics/static_dashboard/index.tsx @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { controlGroupInputBuilder } from '@kbn/controls-plugin/public'; import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common'; import { NotificationsStart } from '@kbn/core/public'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../../../common/data_view_constants'; +import { useDataViewId } from '../../../../hooks/use_data_view_id'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, @@ -28,11 +28,11 @@ import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plug import { useApmDataView } from '../../../../hooks/use_apm_data_view'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../../hooks/use_apm_params'; - import { getDashboardPanelMap, MetricsDashboardProps } from './helper'; export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) { const [dashboard, setDashboard] = useState(); + const dataViewId = useDataViewId(); const { query: { environment, kuery, rangeFrom, rangeTo }, @@ -65,7 +65,7 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) { return ( - getCreationOptions(dashboardProps, notifications) + getCreationOptions(dashboardProps, notifications, dataViewId) } ref={setDashboard} /> @@ -74,20 +74,21 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) { async function getCreationOptions( dashboardProps: MetricsDashboardProps, - notifications: NotificationsStart + notifications: NotificationsStart, + dataViewId: string ): Promise { try { const builder = controlGroupInputBuilder; const controlGroupInput = getDefaultControlGroupInput(); await builder.addDataControlFromField(controlGroupInput, { - dataViewId: APM_STATIC_DATA_VIEW_ID, + dataViewId, title: 'Node name', fieldName: 'service.node.name', width: 'medium', grow: true, }); - const panels = await getDashboardPanelMap(dashboardProps); + const panels = await getDashboardPanelMap(dashboardProps, dataViewId); if (!panels) { throw new Error('Failed parsing dashboard panels.'); diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/embedded_map.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/embedded_map.tsx index b046511d6ce64..f4c530b47acc2 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/embedded_map.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/embedded_map.tsx @@ -23,6 +23,7 @@ import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; +import { useDataViewId } from '../../../../../hooks/use_data_view_id'; import { ApmPluginStartDeps } from '../../../../../plugin'; import { getLayerList } from './map_layers/get_layer_list'; import { MapTypes } from '../../../../../../common/mobile/constants'; @@ -40,6 +41,7 @@ function EmbeddedMapComponent({ filters: Filter[]; }) { const [error, setError] = useState(); + const dataViewId = useDataViewId(); const [embeddable, setEmbeddable] = useState< MapEmbeddable | ErrorEmbeddable | undefined @@ -128,7 +130,7 @@ function EmbeddedMapComponent({ useEffect(() => { const setLayerList = async () => { if (embeddable && !isErrorEmbeddable(embeddable)) { - const layerList = await getLayerList({ selectedMap, maps }); + const layerList = await getLayerList({ selectedMap, maps, dataViewId }); await Promise.all([ embeddable.setLayerList(layerList), embeddable.reload(), @@ -137,7 +139,7 @@ function EmbeddedMapComponent({ }; setLayerList(); - }, [embeddable, selectedMap, maps]); + }, [embeddable, selectedMap, maps, dataViewId]); useEffect(() => { if (embeddable) { diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_http_requests_map_layer_list.ts b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_http_requests_map_layer_list.ts index 8954626546290..728658817b27a 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_http_requests_map_layer_list.ts +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_http_requests_map_layer_list.ts @@ -24,7 +24,6 @@ import { SPAN_SUBTYPE, SPAN_TYPE, } from '../../../../../../../common/es_fields/apm'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../../../../../common/data_view_constants'; import { getLayerStyle, PalleteColors } from './get_map_layer_style'; import { MobileSpanSubtype, @@ -48,7 +47,10 @@ const label = i18n.translate( } ); -export async function getHttpRequestsLayerList(maps?: MapsStartApi) { +export async function getHttpRequestsLayerList( + maps: MapsStartApi | undefined, + dataViewId: string +) { const whereQuery = { language: 'kuery', query: `${PROCESSOR_EVENT}:${ProcessorEvent.span} and ${SPAN_SUBTYPE}:${MobileSpanSubtype.Http} and ${SPAN_TYPE}:${MobileSpanType.External}`, @@ -72,7 +74,7 @@ export async function getHttpRequestsLayerList(maps?: MapsStartApi) { }, ], whereQuery, - indexPatternId: APM_STATIC_DATA_VIEW_ID, + indexPatternId: dataViewId, applyGlobalQuery: true, applyGlobalTime: true, applyForceRefresh: true, @@ -114,7 +116,7 @@ export async function getHttpRequestsLayerList(maps?: MapsStartApi) { }, ], whereQuery, - indexPatternId: APM_STATIC_DATA_VIEW_ID, + indexPatternId: dataViewId, applyGlobalQuery: true, applyGlobalTime: true, applyForceRefresh: true, diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_layer_list.ts b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_layer_list.ts index 152f1874b143b..d9f1d023fbf64 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_layer_list.ts +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_layer_list.ts @@ -10,19 +10,21 @@ import { getHttpRequestsLayerList } from './get_http_requests_map_layer_list'; import { getSessionMapLayerList } from './get_session_map_layer_list'; import { MapTypes } from '../../../../../../../common/mobile/constants'; -export async function getLayerList({ +export function getLayerList({ selectedMap, maps, + dataViewId, }: { selectedMap: MapTypes; - maps?: MapsStartApi; + maps: MapsStartApi | undefined; + dataViewId: string; }): Promise { switch (selectedMap) { case MapTypes.Http: - return await getHttpRequestsLayerList(maps); + return getHttpRequestsLayerList(maps, dataViewId); case MapTypes.Session: - return await getSessionMapLayerList(maps); + return getSessionMapLayerList(maps, dataViewId); default: - return await getHttpRequestsLayerList(maps); + return getHttpRequestsLayerList(maps, dataViewId); } } diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_session_map_layer_list.ts b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_session_map_layer_list.ts index a0d2b80b218fd..be86b5da710a8 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_session_map_layer_list.ts +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/geo_map/map_layers/get_session_map_layer_list.ts @@ -21,7 +21,6 @@ import { CLIENT_GEO_REGION_ISO_CODE, SESSION_ID, } from '../../../../../../../common/es_fields/apm'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../../../../../common/data_view_constants'; import { getLayerStyle, PalleteColors } from './get_map_layer_style'; interface VectorLayerDescriptor extends BaseVectorLayerDescriptor { @@ -40,7 +39,10 @@ const label = i18n.translate( defaultMessage: 'Sessions', } ); -export async function getSessionMapLayerList(maps?: MapsStartApi) { +export async function getSessionMapLayerList( + maps: MapsStartApi | undefined, + dataViewId: string +) { const basemapLayerDescriptor = await maps?.createLayerDescriptors?.createBasemapLayerDescriptor(); @@ -59,7 +61,7 @@ export async function getSessionMapLayerList(maps?: MapsStartApi) { label, }, ], - indexPatternId: APM_STATIC_DATA_VIEW_ID, + indexPatternId: dataViewId, applyGlobalQuery: true, applyGlobalTime: true, applyForceRefresh: true, @@ -101,7 +103,7 @@ export async function getSessionMapLayerList(maps?: MapsStartApi) { label, }, ], - indexPatternId: APM_STATIC_DATA_VIEW_ID, + indexPatternId: dataViewId, applyGlobalQuery: true, applyGlobalTime: true, applyForceRefresh: true, diff --git a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx index dccce740b62d3..1f066d1322eb6 100644 --- a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import rison from '@kbn/rison'; import url from 'url'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../../../common/data_view_constants'; +import { useDataViewId } from '../../../../hooks/use_data_view_id'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { getTimepickerRisonData } from '../rison_helpers'; @@ -37,16 +37,18 @@ export const getDiscoverHref = ({ basePath, location, query, + dataViewId, }: { basePath: IBasePath; location: Location; query: Props['query']; + dataViewId: string; }) => { const risonQuery = { _g: getTimepickerRisonData(location.search), _a: { ...query._a, - index: APM_STATIC_DATA_VIEW_ID, + index: dataViewId, }, }; @@ -62,11 +64,13 @@ export const getDiscoverHref = ({ export function DiscoverLink({ query = {}, ...rest }: Props) { const { core } = useApmPluginContext(); const location = useLocation(); + const dataViewId = useDataViewId(); const href = getDiscoverHref({ basePath: core.http.basePath, query, location, + dataViewId, }); return ; diff --git a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_links.integration.test.tsx b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_links.integration.test.tsx index dfd14e3a172b9..f4d95a87df5dc 100644 --- a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_links.integration.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_links.integration.test.tsx @@ -35,7 +35,7 @@ describe('DiscoverLinks', () => { ); expect(href).toMatchInlineSnapshot( - `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:'processor.event:\\"transaction\\" AND transaction.id:\\"8b60bd32ecc6e150\\" AND trace.id:\\"8b60bd32ecc6e1506735a8b6cfcf175c\\"'))"` + `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:'processor.event:\\"transaction\\" AND transaction.id:\\"8b60bd32ecc6e150\\" AND trace.id:\\"8b60bd32ecc6e1506735a8b6cfcf175c\\"'))"` ); }); @@ -55,7 +55,7 @@ describe('DiscoverLinks', () => { ); expect(href).toMatchInlineSnapshot( - `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:'span.id:\\"test-span-id\\"'))"` + `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:'span.id:\\"test-span-id\\"'))"` ); }); @@ -77,7 +77,7 @@ describe('DiscoverLinks', () => { ); expect(href).toMatchInlineSnapshot( - `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\"'),sort:('@timestamp':desc))"` + `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\"'),sort:('@timestamp':desc))"` ); }); @@ -100,7 +100,7 @@ describe('DiscoverLinks', () => { ); expect(href).toMatchInlineSnapshot( - `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\" AND some:kuery-string'),sort:('@timestamp':desc))"` + `"/basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:'service.name:\\"service-name\\" AND error.grouping_key:\\"grouping-key\\" AND some:kuery-string'),sort:('@timestamp':desc))"` ); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts index fe8cb9d68fdfb..7d7a720f27cfc 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts @@ -77,6 +77,7 @@ describe('Transaction action menu', () => { rangeFrom: 'now-24h', rangeTo: 'now', environment: 'ENVIRONMENT_ALL', + dataViewId: 'apm_static_data_view_id_default', }) ).toEqual([ [ @@ -113,7 +114,7 @@ describe('Transaction action menu', () => { { key: 'sampleDocument', label: 'View transaction in Discover', - href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', + href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', condition: true, }, ], @@ -145,6 +146,7 @@ describe('Transaction action menu', () => { rangeFrom: 'now-24h', rangeTo: 'now', environment: 'ENVIRONMENT_ALL', + dataViewId: 'apm_static_data_view_id_default', }) ).toEqual([ [ @@ -200,7 +202,7 @@ describe('Transaction action menu', () => { { key: 'sampleDocument', label: 'View transaction in Discover', - href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', + href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', condition: true, }, ], @@ -232,6 +234,7 @@ describe('Transaction action menu', () => { rangeFrom: 'now-24h', rangeTo: 'now', environment: 'ENVIRONMENT_ALL', + dataViewId: 'apm_static_data_view_id_default', }) ).toEqual([ [ @@ -286,7 +289,7 @@ describe('Transaction action menu', () => { { key: 'sampleDocument', label: 'View transaction in Discover', - href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_index_pattern_id,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', + href: 'some-basepath/app/discover#/?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(index:apm_static_data_view_id_default,interval:auto,query:(language:kuery,query:\'processor.event:"transaction" AND transaction.id:"123" AND trace.id:"123"\'))', condition: true, }, ], diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts index a944a80a458f7..b50bbd5239ccb 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts @@ -55,6 +55,7 @@ export const getSections = ({ allDatasetsLocator, logsLocator, nodeLogsLocator, + dataViewId, }: { transaction?: Transaction; basePath: IBasePath; @@ -68,6 +69,7 @@ export const getSections = ({ allDatasetsLocator: LocatorPublic; logsLocator: LocatorPublic; nodeLogsLocator: LocatorPublic; + dataViewId: string; }) => { if (!transaction) return []; @@ -271,6 +273,7 @@ export const getSections = ({ basePath, query: getDiscoverQuery(transaction), location, + dataViewId, }), condition: true, }, diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx index 02664e370b9c3..7895fdf7d059d 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx @@ -32,6 +32,7 @@ import { NodeLogsLocatorParams, } from '@kbn/logs-shared-plugin/common'; import type { ProfilingLocators } from '@kbn/observability-shared-plugin/public'; +import { useDataViewId } from '../../../hooks/use_data_view_id'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { ApmFeatureFlagName } from '../../../../common/apm_feature_flags'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; @@ -138,6 +139,7 @@ function ActionMenuSections({ const { core, uiActions, share } = useApmPluginContext(); const location = useLocation(); const apmRouter = useApmRouter(); + const dataViewId = useDataViewId(); const allDatasetsLocator = share.url.locators.get( ALL_DATASETS_LOCATOR_ID @@ -173,6 +175,7 @@ function ActionMenuSections({ allDatasetsLocator, logsLocator, nodeLogsLocator, + dataViewId, }); const externalMenuItems = useAsync(() => { diff --git a/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts b/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts index 2f2d14714e513..57ad95dce2a5a 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts +++ b/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts @@ -12,11 +12,11 @@ import { useEffect, useState } from 'react'; import { ApmPluginStartDeps } from '../plugin'; import { callApmApi } from '../services/rest/create_call_apm_api'; -async function getApmDataViewTitle() { - const res = await callApmApi('GET /internal/apm/data_view/title', { +async function getApmDataViewIndexPattern() { + const res = await callApmApi('GET /internal/apm/data_view/index_pattern', { signal: null, }); - return res.apmDataViewTitle; + return res.apmDataViewIndexPattern; } export function useApmDataView() { @@ -25,11 +25,11 @@ export function useApmDataView() { useEffect(() => { async function fetchDataView() { - const title = await getApmDataViewTitle(); + const indexPattern = await getApmDataViewIndexPattern(); try { const displayError = false; return await services.dataViews.create( - { title }, + { title: indexPattern }, undefined, displayError ); diff --git a/x-pack/plugins/apm/public/hooks/use_data_view_id.tsx b/x-pack/plugins/apm/public/hooks/use_data_view_id.tsx new file mode 100644 index 0000000000000..3390471ff60e7 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_data_view_id.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useEffect, useState } from 'react'; +import { getDataViewId } from '../../common/data_view_constants'; +import { ApmPluginStartDeps } from '../plugin'; + +export function useDataViewId() { + const [dataViewId, setDataViewId] = useState( + getDataViewId('default') + ); + const { spaces } = useKibana().services; + + useEffect(() => { + const fetchSpaceId = async () => { + const space = await spaces?.getActiveSpace(); + setDataViewId(getDataViewId(space?.id ?? 'default')); + }; + + fetchSpaceId(); + }, [spaces]); + + return dataViewId; +} diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 525b2c5e2cbc5..07010d5f8dc5b 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -11,7 +11,6 @@ import { Logger, Plugin, PluginInitializerContext, - SavedObjectsClient, } from '@kbn/core/server'; import { isEmpty, mapValues } from 'lodash'; import { Dataset } from '@kbn/rule-registry-plugin/server'; @@ -50,7 +49,6 @@ import { scheduleSourceMapMigration } from './routes/source_maps/schedule_source import { createApmSourceMapIndexTemplate } from './routes/source_maps/create_apm_source_map_index_template'; import { addApiKeysToEveryPackagePolicyIfMissing } from './routes/fleet/api_keys/add_api_keys_to_policies_if_missing'; import { apmTutorialCustomIntegration } from '../common/tutorial/tutorials'; -import { APM_STATIC_DATA_VIEW_ID } from '../common/data_view_constants'; export class APMPlugin implements @@ -123,26 +121,6 @@ export class APMPlugin ], }); - // ensure that the APM data view is globally available - getCoreStart() - .then(async (coreStart) => { - const soClient = new SavedObjectsClient( - coreStart.savedObjects.createInternalRepository() - ); - - await soClient.updateObjectsSpaces( - [{ id: APM_STATIC_DATA_VIEW_ID, type: 'index-pattern' }], - ['*'], - [] - ); - }) - .catch((e) => { - this.logger?.error( - 'Failed to make APM data view available globally', - e - ); - }); - const resourcePlugins = mapValues(plugins, (value, key) => { return { setup: value, diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts index 714f97b1a4801..c3c1d7f124a54 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts @@ -5,20 +5,22 @@ * 2.0. */ -import { createStaticDataView } from './create_static_data_view'; -import * as HistoricalAgentData from '../historical_data/has_historical_agent_data'; +import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; +import { Logger } from '@kbn/core/server'; import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { APMCore } from '../typings'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; -import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; import { APMRouteHandlerResources } from '../apm_routes/register_apm_server_routes'; +import * as HistoricalAgentData from '../historical_data/has_historical_agent_data'; +import { APMCore } from '../typings'; +import { createStaticDataView } from './create_static_data_view'; function getMockedDataViewService(existingDataViewTitle: string) { return { get: jest.fn(() => ({ - title: existingDataViewTitle, + getIndexPattern: () => existingDataViewTitle, })), createAndSave: jest.fn(), + delete: () => {}, } as unknown as DataViewsService; } @@ -36,6 +38,10 @@ const coreMock = { }, } as unknown as APMCore; +const logger = { + info: jest.fn, +} as unknown as Logger; + const apmEventClientMock = { search: jest.fn(), indices: { @@ -55,6 +61,8 @@ describe('createStaticDataView', () => { config: { autoCreateApmDataView: false }, } as APMRouteHandlerResources, dataViewService, + spaceId: 'default', + logger, }); expect(dataViewService.createAndSave).not.toHaveBeenCalled(); }); @@ -73,6 +81,8 @@ describe('createStaticDataView', () => { config: { autoCreateApmDataView: false }, } as APMRouteHandlerResources, dataViewService, + spaceId: 'default', + logger, }); expect(dataViewService.createAndSave).not.toHaveBeenCalled(); }); @@ -92,6 +102,8 @@ describe('createStaticDataView', () => { config: { autoCreateApmDataView: true }, } as APMRouteHandlerResources, dataViewService, + spaceId: 'default', + logger, }); expect(dataViewService.createAndSave).toHaveBeenCalled(); @@ -114,6 +126,8 @@ describe('createStaticDataView', () => { config: { autoCreateApmDataView: true }, } as APMRouteHandlerResources, dataViewService, + spaceId: 'default', + logger, }); expect(dataViewService.get).toHaveBeenCalled(); @@ -143,6 +157,8 @@ describe('createStaticDataView', () => { config: { autoCreateApmDataView: true }, } as APMRouteHandlerResources, dataViewService, + spaceId: 'default', + logger, }); expect(dataViewService.get).toHaveBeenCalled(); diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts index 715a568eaf6bd..5f2cf097f3e2e 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts @@ -5,18 +5,21 @@ * 2.0. */ -import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { DataView, DataViewsService } from '@kbn/data-views-plugin/common'; import { i18n } from '@kbn/i18n'; +import { + DO_NOT_USE_LEGACY_APM_STATIC_DATA_VIEW_ID, + getDataViewId, +} from '../../../common/data_view_constants'; import { TRACE_ID, TRANSACTION_ID, TRANSACTION_DURATION, } from '../../../common/es_fields/apm'; -import { APM_STATIC_DATA_VIEW_ID } from '../../../common/data_view_constants'; import { hasHistoricalAgentData } from '../historical_data/has_historical_agent_data'; import { withApmSpan } from '../../utils/with_apm_span'; -import { getApmDataViewTitle } from './get_apm_data_view_title'; +import { getApmDataViewIndexPattern } from './get_apm_data_view_index_pattern'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { APMRouteHandlerResources } from '../apm_routes/register_apm_server_routes'; @@ -29,12 +32,18 @@ export async function createStaticDataView({ dataViewService, resources, apmEventClient, + spaceId, + logger, }: { dataViewService: DataViewsService; resources: APMRouteHandlerResources; apmEventClient: APMEventClient; + spaceId: string; + logger: Logger; }): CreateDataViewResponse { const { config } = resources; + const dataViewId = getDataViewId(spaceId); + logger.info(`create static data view ${dataViewId}`); return withApmSpan('create_static_data_view', async () => { // don't auto-create APM data view if it's been disabled via the config @@ -61,10 +70,13 @@ export async function createStaticDataView({ }; } - const apmDataViewTitle = getApmDataViewTitle(apmEventClient.indices); + const apmDataViewIndexPattern = getApmDataViewIndexPattern( + apmEventClient.indices + ); const shouldCreateOrUpdate = await getShouldCreateOrUpdate({ - apmDataViewTitle, + apmDataViewIndexPattern, dataViewService, + dataViewId, }); if (!shouldCreateOrUpdate) { @@ -72,52 +84,48 @@ export async function createStaticDataView({ created: false, reason: i18n.translate( 'xpack.apm.dataView.alreadyExistsInActiveSpace', - { defaultMessage: 'Dataview already exists in the active space' } + { + defaultMessage: + 'Dataview already exists in the active space and does not need to be updated', + } ), }; } - return await withApmSpan('create_data_view', async () => { - try { - const dataView = await createAndSaveStaticDataView({ - dataViewService, - apmDataViewTitle, - }); - - await addDataViewToAllSpaces(resources); - - return { created: true, dataView }; - } catch (e) { - // if the data view (saved object) already exists a conflict error (code: 409) will be thrown - if (SavedObjectsErrorHelpers.isConflictError(e)) { - return { - created: false, - reason: i18n.translate( - 'xpack.apm.dataView.alreadyExistsInAnotherSpace', - { - defaultMessage: - 'Dataview already exists in another space but is not made available in this space', - } - ), - }; - } + // delete legacy global data view + + const dataView = await createAndSaveStaticDataView({ + dataViewService, + apmDataViewIndexPattern, + dataViewId, + }); + + try { + await dataViewService.delete(DO_NOT_USE_LEGACY_APM_STATIC_DATA_VIEW_ID); + } catch (e) { + // swallow error if caused by the data view (saved object) not existing + if (!SavedObjectsErrorHelpers.isNotFoundError(e)) { throw e; } - }); + } + + return { created: true, dataView }; }); } // only create data view if it doesn't exist or was changed async function getShouldCreateOrUpdate({ dataViewService, - apmDataViewTitle, + apmDataViewIndexPattern, + dataViewId, }: { dataViewService: DataViewsService; - apmDataViewTitle: string; + apmDataViewIndexPattern: string; + dataViewId: string; }) { try { - const existingDataView = await dataViewService.get(APM_STATIC_DATA_VIEW_ID); - return existingDataView.title !== apmDataViewTitle; + const existingDataView = await dataViewService.get(dataViewId); + return existingDataView.getIndexPattern() !== apmDataViewIndexPattern; } catch (e) { // ignore exception if the data view (saved object) is not found if (SavedObjectsErrorHelpers.isNotFoundError(e)) { @@ -128,32 +136,21 @@ async function getShouldCreateOrUpdate({ } } -async function addDataViewToAllSpaces(resources: APMRouteHandlerResources) { - const { request, core } = resources; - const startServices = await core.start(); - const scopedClient = startServices.savedObjects.getScopedClient(request); - - // make data view available across all spaces - return scopedClient.updateObjectsSpaces( - [{ id: APM_STATIC_DATA_VIEW_ID, type: 'index-pattern' }], - ['*'], - [] - ); -} - function createAndSaveStaticDataView({ dataViewService, - apmDataViewTitle, + apmDataViewIndexPattern, + dataViewId, }: { dataViewService: DataViewsService; - apmDataViewTitle: string; + apmDataViewIndexPattern: string; + dataViewId: string; }) { return dataViewService.createAndSave( { allowNoIndex: true, - id: APM_STATIC_DATA_VIEW_ID, + id: dataViewId, name: 'APM', - title: apmDataViewTitle, + title: apmDataViewIndexPattern, timeFieldName: '@timestamp', // link to APM from Discover diff --git a/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_title.test.ts b/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_index_pattern.test.ts similarity index 69% rename from x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_title.test.ts rename to x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_index_pattern.test.ts index a8dc25e493006..e7a0ee915c8cc 100644 --- a/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_title.test.ts +++ b/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_index_pattern.test.ts @@ -6,23 +6,23 @@ */ import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; -import { getApmDataViewTitle } from './get_apm_data_view_title'; +import { getApmDataViewIndexPattern } from './get_apm_data_view_index_pattern'; -describe('getApmDataViewTitle', () => { - it('returns a data view title by combining existing indicies', () => { - const title = getApmDataViewTitle({ +describe('getApmDataViewIndexPattern', () => { + it('returns a data view index pattern by combining existing indices', () => { + const indexPattern = getApmDataViewIndexPattern({ transaction: 'apm-*-transaction-*', span: 'apm-*-span-*', error: 'apm-*-error-*', metric: 'apm-*-metrics-*', } as APMIndices); - expect(title).toBe( + expect(indexPattern).toBe( 'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*' ); }); it('removes duplicates', () => { - const title = getApmDataViewTitle({ + const title = getApmDataViewIndexPattern({ transaction: 'apm-*', span: 'apm-*', error: 'apm-*', diff --git a/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_title.ts b/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_index_pattern.ts similarity index 87% rename from x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_title.ts rename to x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_index_pattern.ts index 3061a1ac15714..b5c3fa0f73d28 100644 --- a/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_title.ts +++ b/x-pack/plugins/apm/server/routes/data_view/get_apm_data_view_index_pattern.ts @@ -8,7 +8,7 @@ import { uniq } from 'lodash'; import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; -export function getApmDataViewTitle(apmIndices: APMIndices) { +export function getApmDataViewIndexPattern(apmIndices: APMIndices) { return uniq([ apmIndices.transaction, apmIndices.span, diff --git a/x-pack/plugins/apm/server/routes/data_view/route.ts b/x-pack/plugins/apm/server/routes/data_view/route.ts index 388884c4f5761..429b74c99e63a 100644 --- a/x-pack/plugins/apm/server/routes/data_view/route.ts +++ b/x-pack/plugins/apm/server/routes/data_view/route.ts @@ -5,22 +5,32 @@ * 2.0. */ +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { CreateDataViewResponse, createStaticDataView, } from './create_static_data_view'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; -import { getApmDataViewTitle } from './get_apm_data_view_title'; +import { getApmDataViewIndexPattern } from './get_apm_data_view_index_pattern'; import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const staticDataViewRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/data_view/static', options: { tags: ['access:apm'] }, handler: async (resources): CreateDataViewResponse => { - const { context, plugins, request } = resources; + const { context, plugins, request, logger } = resources; const apmEventClient = await getApmEventClient(resources); const coreContext = await context.core; + // get name of selected (name)space + const spacesStart = await plugins.spaces?.start(); + const spaceId = + spacesStart?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + + if (!spaceId) { + throw new Error('No spaceId found'); + } + const dataViewStart = await plugins.dataViews.start(); const dataViewService = await dataViewStart.dataViewsServiceFactory( coreContext.savedObjects.client, @@ -33,6 +43,8 @@ const staticDataViewRoute = createApmServerRoute({ dataViewService, resources, apmEventClient, + spaceId, + logger, }); return res; @@ -40,13 +52,15 @@ const staticDataViewRoute = createApmServerRoute({ }); const dataViewTitleRoute = createApmServerRoute({ - endpoint: 'GET /internal/apm/data_view/title', + endpoint: 'GET /internal/apm/data_view/index_pattern', options: { tags: ['access:apm'] }, - handler: async ({ getApmIndices }): Promise<{ apmDataViewTitle: string }> => { + handler: async ({ + getApmIndices, + }): Promise<{ apmDataViewIndexPattern: string }> => { const apmIndicies = await getApmIndices(); - const apmDataViewTitle = getApmDataViewTitle(apmIndicies); + const apmDataViewIndexPattern = getApmDataViewIndexPattern(apmIndicies); - return { apmDataViewTitle }; + return { apmDataViewIndexPattern }; }, }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c656e54ee8f5f..f55b85b97b539 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -8684,7 +8684,6 @@ "xpack.apm.dashboard.addDashboard.useContextFilterLabel.tooltip": "L'activation de cette option applique des filtres au tableau de bord en fonction du service et de l'environnement choisis.", "xpack.apm.data_view.creation_failed": "Une erreur s'est produite lors de la création de la vue de données", "xpack.apm.dataView.alreadyExistsInActiveSpace": "La vue de données existe déjà dans l'espace actif", - "xpack.apm.dataView.alreadyExistsInAnotherSpace": "La vue de données existe déjà dans un autre espace mais elle n'est pas disponible dans cet espace", "xpack.apm.dataView.autoCreateDisabled": "La création automatique des vues de données a été désactivée via l'option de configuration \"autoCreateApmDataView\"", "xpack.apm.dataView.noApmData": "Aucune donnée APM", "xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "Toutes les opérations", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 157391ad9a6ff..a04e7f46cf676 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8699,7 +8699,6 @@ "xpack.apm.dashboard.addDashboard.useContextFilterLabel.tooltip": "このオプションを有効にすると、選択したサービスと環境に基づいてダッシュボードにフィルターが適用されます。", "xpack.apm.data_view.creation_failed": "データビューの作成中にエラーが発生しました", "xpack.apm.dataView.alreadyExistsInActiveSpace": "アクティブなスペースにはすでにデータビューが存在します", - "xpack.apm.dataView.alreadyExistsInAnotherSpace": "データビューはすでに別のスペースに存在しますが、このスペースでは使用できません", "xpack.apm.dataView.autoCreateDisabled": "データビューの自動作成は、「autoCreateApmDataView」構成オプションによって無効化されています", "xpack.apm.dataView.noApmData": "APMデータがありません", "xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "すべての演算", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 192297f53e6ec..5077a6c337332 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8698,7 +8698,6 @@ "xpack.apm.dashboard.addDashboard.useContextFilterLabel.tooltip": "启用此选项会根据您选择的服务和环境,将筛选应用于仪表板。", "xpack.apm.data_view.creation_failed": "创建数据视图时出错", "xpack.apm.dataView.alreadyExistsInActiveSpace": "活动工作区中已存在数据视图", - "xpack.apm.dataView.alreadyExistsInAnotherSpace": "数据视图已在另一工作区中存在,但在此工作区中不可用", "xpack.apm.dataView.autoCreateDisabled": "已通过“autoCreateApmDataView”配置选项禁止自动创建数据视图", "xpack.apm.dataView.noApmData": "无 APM 数据", "xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "所有操作", diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts index 4df3c8c1e8470..46ab3ddff2529 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts @@ -39,7 +39,7 @@ async function getCoreWebVitalsResponse({ dataStartPlugin, }: WithDataPlugin) { const dataViewResponse = await callApmApi( - 'GET /internal/apm/data_view/title', + 'GET /internal/apm/data_view/index_pattern', { signal: null, } @@ -47,7 +47,7 @@ async function getCoreWebVitalsResponse({ return await esQuery>(dataStartPlugin, { params: { - index: dataViewResponse.apmDataViewTitle, + index: dataViewResponse.apmDataViewIndexPattern, ...coreWebVitalsQuery(absoluteTime.start, absoluteTime.end, undefined, { serviceName: serviceName ? [serviceName] : undefined, }), @@ -83,7 +83,7 @@ export async function hasRumData( params: WithDataPlugin ): Promise { const dataViewResponse = await callApmApi( - 'GET /internal/apm/data_view/title', + 'GET /internal/apm/data_view/index_pattern', { signal: null, } @@ -93,7 +93,7 @@ export async function hasRumData( params.dataStartPlugin, { params: { - index: dataViewResponse.apmDataViewTitle, + index: dataViewResponse.apmDataViewIndexPattern, ...hasRumDataQuery({ start: params?.absoluteTime?.start, end: params?.absoluteTime?.end, @@ -102,7 +102,10 @@ export async function hasRumData( } ); - return formatHasRumResult(esQueryResponse, dataViewResponse.apmDataViewTitle); + return formatHasRumResult( + esQueryResponse, + dataViewResponse.apmDataViewIndexPattern + ); } async function esQuery( diff --git a/x-pack/plugins/ux/public/hooks/use_dynamic_data_view.ts b/x-pack/plugins/ux/public/hooks/use_dynamic_data_view.ts index 81fb2f966966b..d051e07d0057d 100644 --- a/x-pack/plugins/ux/public/hooks/use_dynamic_data_view.ts +++ b/x-pack/plugins/ux/public/hooks/use_dynamic_data_view.ts @@ -9,13 +9,13 @@ import { useFetcher } from './use_fetcher'; export function useDynamicDataViewTitle() { const { data, status } = useFetcher((callApmApi) => { - return callApmApi('GET /internal/apm/data_view/title', { + return callApmApi('GET /internal/apm/data_view/index_pattern', { isCachable: true, }); }, []); return { - dataViewTitle: data?.apmDataViewTitle, + dataViewTitle: data?.apmDataViewIndexPattern, status, }; } diff --git a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts index ac16abff4098b..4fe7ae4a62e71 100644 --- a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts +++ b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts @@ -20,6 +20,7 @@ export function createApmApiClient(st: supertest.SuperTest) { options: { type?: 'form-data'; endpoint: TEndpoint; + spaceId?: string; } & APIClientRequestParamsOf & { params?: { query?: { _inspect?: boolean } } } ): Promise> => { const { endpoint, type } = options; @@ -27,7 +28,8 @@ export function createApmApiClient(st: supertest.SuperTest) { const params = 'params' in options ? (options.params as Record) : {}; const { method, pathname, version } = formatRequest(endpoint, params.path); - const url = format({ pathname, query: params?.query }); + const pathnameWithSpaceId = options.spaceId ? `/s/${options.spaceId}${pathname}` : pathname; + const url = format({ pathname: pathnameWithSpaceId, query: params?.query }); const headers: Record = { 'kbn-xsrf': 'foo', diff --git a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts index a49e698c86c65..dcc8fc76044cf 100644 --- a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts +++ b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts @@ -8,7 +8,7 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import expect from '@kbn/expect'; -import { APM_STATIC_DATA_VIEW_ID } from '@kbn/apm-plugin/common/data_view_constants'; +import { getDataViewId } from '@kbn/apm-plugin/common/data_view_constants'; import { DataView } from '@kbn/data-views-plugin/common'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import request from 'superagent'; @@ -20,28 +20,33 @@ export default function ApiTest({ getService }: FtrProviderContext) { const apmApiClient = getService('apmApiClient'); const supertest = getService('supertest'); const synthtrace = getService('synthtraceEsClient'); + const logger = getService('log'); const dataViewPattern = 'traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*'; - function createDataViewWithWriteUser() { + function createDataViewWithWriteUser({ spaceId }: { spaceId: string }) { return apmApiClient.writeUser({ endpoint: 'POST /internal/apm/data_view/static', + spaceId, }); } - function createDataViewWithReadUser() { - return apmApiClient.readUser({ endpoint: 'POST /internal/apm/data_view/static' }); + function createDataViewWithReadUser({ spaceId }: { spaceId: string }) { + return apmApiClient.readUser({ + endpoint: 'POST /internal/apm/data_view/static', + spaceId, + }); } - function deleteDataView() { + function deleteDataView(spaceId: string) { return supertest - .delete(`/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}?force=true`) + .delete(`/s/${spaceId}/api/saved_objects/index-pattern/${getDataViewId(spaceId)}?force=true`) .set('kbn-xsrf', 'foo'); } - function getDataView({ space }: { space: string }) { - const spacePrefix = space !== 'default' ? `/s/${space}` : ''; + function getDataView({ spaceId }: { spaceId: string }) { + const spacePrefix = spaceId !== 'default' ? `/s/${spaceId}` : ''; return supertest.get( - `${spacePrefix}/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}` + `${spacePrefix}/api/saved_objects/index-pattern/${getDataViewId(spaceId)}` ); } @@ -56,7 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('no mappings exist', { config: 'basic', archives: [] }, () => { let response: SupertestReturnType<'POST /internal/apm/data_view/static'>; before(async () => { - response = await createDataViewWithWriteUser(); + response = await createDataViewWithWriteUser({ spaceId: 'default' }); }); it('does not create data view', async () => { @@ -68,10 +73,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('cannot fetch data view', async () => { - const res = await getDataView({ space: 'default' }); + const res = await getDataView({ spaceId: 'default' }); expect(res.status).to.be(404); expect(res.body.message).to.eql( - 'Saved object [index-pattern/apm_static_index_pattern_id] not found' + 'Saved object [index-pattern/apm_static_data_view_id_default] not found' ); }); }); @@ -86,14 +91,18 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); afterEach(async () => { - await deleteDataView(); + try { + await Promise.all([deleteDataView('default'), deleteDataView('foo')]); + } catch (e) { + logger.error(`Could not delete data views ${e.message}`); + } }); describe('when creating data view with write user', () => { let response: SupertestReturnType<'POST /internal/apm/data_view/static'>; before(async () => { - response = await createDataViewWithWriteUser(); + response = await createDataViewWithWriteUser({ spaceId: 'default' }); }); it('successfully creates the apm data view', async () => { @@ -102,7 +111,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { // @ts-expect-error const dataView = response.body.dataView as DataView; - expect(dataView.id).to.be('apm_static_index_pattern_id'); + expect(dataView.id).to.be('apm_static_data_view_id_default'); expect(dataView.name).to.be('APM'); expect(dataView.title).to.be('traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*'); }); @@ -112,8 +121,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { let dataViewResponse: request.Response; before(async () => { - await createDataViewWithWriteUser(); - dataViewResponse = await getDataView({ space: 'default' }); + await createDataViewWithWriteUser({ spaceId: 'default' }); + dataViewResponse = await getDataView({ spaceId: 'default' }); }); it('return 200', () => { @@ -121,7 +130,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('has correct id', () => { - expect(dataViewResponse.body.id).to.be('apm_static_index_pattern_id'); + expect(dataViewResponse.body.id).to.be('apm_static_data_view_id_default'); }); it('has correct title', () => { @@ -170,7 +179,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('when creating data view via read user', () => { it('throws an error', async () => { try { - await createDataViewWithReadUser(); + await createDataViewWithReadUser({ spaceId: 'default' }); } catch (e) { const err = e as ApmApiError; const responseBody = err.res.body; @@ -184,30 +193,44 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('when creating data view twice', () => { it('returns 200 response with reason, if data view already exists', async () => { - await createDataViewWithWriteUser(); - const res = await createDataViewWithWriteUser(); + await createDataViewWithWriteUser({ spaceId: 'default' }); + const res = await createDataViewWithWriteUser({ spaceId: 'default' }); expect(res.status).to.be(200); expect(res.body).to.eql({ created: false, - reason: 'Dataview already exists in the active space', + reason: 'Dataview already exists in the active space and does not need to be updated', }); }); }); describe('when creating data view in "default" space', async () => { - it('can be retrieved from the "default space"', async () => { - await createDataViewWithWriteUser(); - const res = await getDataView({ space: 'default' }); - expect(res.body.id).to.eql('apm_static_index_pattern_id'); - expect(res.body.namespaces).to.eql(['*', 'default']); + it('can be retrieved from the "default" space', async () => { + await createDataViewWithWriteUser({ spaceId: 'default' }); + const res = await getDataView({ spaceId: 'default' }); + expect(res.body.id).to.eql('apm_static_data_view_id_default'); + expect(res.body.namespaces).to.eql(['default']); }); + it('cannot be retrieved from the "foo" space', async () => { + await createDataViewWithWriteUser({ spaceId: 'default' }); + const res = await getDataView({ spaceId: 'foo' }); + expect(res.body.statusCode).to.be(404); + }); + }); + + describe('when creating data view in "foo" space', async () => { it('can be retrieved from the "foo" space', async () => { - await createDataViewWithWriteUser(); - const res = await getDataView({ space: 'foo' }); - expect(res.body.id).to.eql('apm_static_index_pattern_id'); - expect(res.body.namespaces).to.eql(['*', 'default']); + await createDataViewWithWriteUser({ spaceId: 'foo' }); + const res = await getDataView({ spaceId: 'foo' }); + expect(res.body.id).to.eql('apm_static_data_view_id_foo'); + expect(res.body.namespaces).to.eql(['foo']); + }); + + it('cannot be retrieved from the "default" space', async () => { + await createDataViewWithWriteUser({ spaceId: 'foo' }); + const res = await getDataView({ spaceId: 'default' }); + expect(res.body.statusCode).to.be(404); }); }); });