Skip to content

Commit

Permalink
[APM] Make data view space aware (#170857)
Browse files Browse the repository at this point in the history
Closes #169924
Related SDH: elastic/sdh-apm#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 <[email protected]>
Co-authored-by: Cauê Marcondes <[email protected]>
  • Loading branch information
3 people authored Nov 20, 2023
1 parent 2a8a68c commit 9523a78
Show file tree
Hide file tree
Showing 28 changed files with 303 additions and 183 deletions.
10 changes: 8 additions & 2 deletions x-pack/plugins/apm/common/data_view_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApmPluginStartDeps>().services;
const dataViewId = useDataViewId();

const [sortField, setSortField] = useState<keyof ApmEvent>('name');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const {
Expand Down Expand Up @@ -103,7 +106,7 @@ export function DiagnosticsApmDocuments() {
language: 'kuery',
query: item.kuery,
},
dataViewId: APM_STATIC_DATA_VIEW_ID,
dataViewId,
timeRange:
rangeTo && rangeFrom
? {
Expand All @@ -123,13 +126,37 @@ export function DiagnosticsApmDocuments() {
{isImported && diagnosticsBundle ? (
<>
<EuiBadge>
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(),
},
}
)}
</EuiBadge>
<EuiBadge>
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(),
},
})}
</EuiBadge>
<EuiBadge>
Filter: {diagnosticsBundle?.params.kuery ?? <em>Empty</em>}
{i18n.translate(
'xpack.apm.diagnosticsApmDocuments.filter:BadgeLabel',
{ defaultMessage: 'Filter:' }
)}
{diagnosticsBundle?.params.kuery ?? (
<em>
{i18n.translate(
'xpack.apm.diagnosticsApmDocuments.em.emptyLabel',
{ defaultMessage: 'Empty' }
)}
</em>
)}
</EuiBadge>
<EuiSpacer />
</>
Expand Down Expand Up @@ -183,7 +210,13 @@ function IntervalDocCount({
<EuiText
css={{ fontStyle: 'italic', fontSize: '80%', display: 'inline' }}
>
({asBigNumber(interval.eventDocCount)} events)
{i18n.translate('xpack.apm.intervalDocCount.TextLabel', {
defaultMessage:
'({docCount} {docCount, plural, one {event} other {events}})',
values: {
docCount: asBigNumber(interval.eventDocCount),
},
})}
</EuiText>
</div>
</EuiToolTip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,7 +28,8 @@ function getDashboardFile({ agentName }: MetricsDashboardProps) {
}

export async function getDashboardPanelMap(
props: MetricsDashboardProps
props: MetricsDashboardProps,
dataViewId: string
): Promise<DashboardPanelMap | undefined> {
const dashboardFile = getDashboardFile(props);
const panelsRawObj = !!dashboardFile
Expand All @@ -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[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<AwaitingDashboardAPI>();
const dataViewId = useDataViewId();

const {
query: { environment, kuery, rangeFrom, rangeTo },
Expand Down Expand Up @@ -65,7 +65,7 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {
return (
<DashboardRenderer
getCreationOptions={() =>
getCreationOptions(dashboardProps, notifications)
getCreationOptions(dashboardProps, notifications, dataViewId)
}
ref={setDashboard}
/>
Expand All @@ -74,20 +74,21 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {

async function getCreationOptions(
dashboardProps: MetricsDashboardProps,
notifications: NotificationsStart
notifications: NotificationsStart,
dataViewId: string
): Promise<DashboardCreationOptions> {
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.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -40,6 +41,7 @@ function EmbeddedMapComponent({
filters: Filter[];
}) {
const [error, setError] = useState<boolean>();
const dataViewId = useDataViewId();

const [embeddable, setEmbeddable] = useState<
MapEmbeddable | ErrorEmbeddable | undefined
Expand Down Expand Up @@ -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(),
Expand All @@ -137,7 +139,7 @@ function EmbeddedMapComponent({
};

setLayerList();
}, [embeddable, selectedMap, maps]);
}, [embeddable, selectedMap, maps, dataViewId]);

useEffect(() => {
if (embeddable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}`,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<LayerDescriptor[]> {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
},
};

Expand All @@ -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 <EuiLink data-test-subj="apmDiscoverLinkLink" {...rest} href={href} />;
Expand Down
Loading

0 comments on commit 9523a78

Please sign in to comment.