[APM] Make data view space aware (#170857)

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 <caue.marcondes@elastic.co>
Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
This commit is contained in:
Søren Louv-Jansen 2023-11-20 14:15:13 +01:00 committed by GitHub
parent 2a8a68c1f8
commit 9523a78e7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 302 additions and 182 deletions

View file

@ -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}`;
}

View file

@ -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 {
@ -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 ? (
<>
<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 />
</>
@ -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>

View file

@ -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<DashboardPanelMap | undefined> {
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[];

View file

@ -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<AwaitingDashboardAPI>();
const dataViewId = useDataViewId();
const {
query: { environment, kuery, rangeFrom, rangeTo },
@ -65,7 +65,7 @@ export function JsonMetricsDashboard(dashboardProps: MetricsDashboardProps) {
return (
<DashboardRenderer
getCreationOptions={() =>
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<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.');

View file

@ -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<boolean>();
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) {

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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,

View file

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

View file

@ -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))"`
);
});
});

View file

@ -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,
},
],

View file

@ -55,6 +55,7 @@ export const getSections = ({
allDatasetsLocator,
logsLocator,
nodeLogsLocator,
dataViewId,
}: {
transaction?: Transaction;
basePath: IBasePath;
@ -68,6 +69,7 @@ export const getSections = ({
allDatasetsLocator: LocatorPublic<AllDatasetsLocatorParams>;
logsLocator: LocatorPublic<LogsLocatorParams>;
nodeLogsLocator: LocatorPublic<NodeLogsLocatorParams>;
dataViewId: string;
}) => {
if (!transaction) return [];
@ -271,6 +273,7 @@ export const getSections = ({
basePath,
query: getDiscoverQuery(transaction),
location,
dataViewId,
}),
condition: true,
},

View file

@ -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<AllDatasetsLocatorParams>(
ALL_DATASETS_LOCATOR_ID
@ -173,6 +175,7 @@ function ActionMenuSections({
allDatasetsLocator,
logsLocator,
nodeLogsLocator,
dataViewId,
});
const externalMenuItems = useAsync(() => {

View file

@ -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
);

View file

@ -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<string>(
getDataViewId('default')
);
const { spaces } = useKibana<ApmPluginStartDeps>().services;
useEffect(() => {
const fetchSpaceId = async () => {
const space = await spaces?.getActiveSpace();
setDataViewId(getDataViewId(space?.id ?? 'default'));
};
fetchSpaceId();
}, [spaces]);
return dataViewId;
}

View file

@ -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,

View file

@ -5,20 +5,22 @@
* 2.0.
*/
import { createStaticDataView } from './create_static_data_view';
import * as HistoricalAgentData from '../historical_data/has_historical_agent_data';
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 { Logger } from '@kbn/core/server';
import { DataViewsService } from '@kbn/data-views-plugin/common';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
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();

View file

@ -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,
});
// delete legacy global data view
await addDataViewToAllSpaces(resources);
const dataView = await createAndSaveStaticDataView({
dataViewService,
apmDataViewIndexPattern,
dataViewId,
});
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',
}
),
};
}
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

View file

@ -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-*',

View file

@ -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,

View file

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

View file

@ -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",

View file

@ -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": "すべての演算",

View file

@ -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": "所有操作",

View file

@ -39,7 +39,7 @@ async function getCoreWebVitalsResponse({
dataStartPlugin,
}: WithDataPlugin<FetchDataParams>) {
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<ReturnType<typeof coreWebVitalsQuery>>(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<HasDataParams>
): Promise<UXHasDataResponse> {
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<T>(

View file

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

View file

@ -20,6 +20,7 @@ export function createApmApiClient(st: supertest.SuperTest<supertest.Test>) {
options: {
type?: 'form-data';
endpoint: TEndpoint;
spaceId?: string;
} & APIClientRequestParamsOf<TEndpoint> & { params?: { query?: { _inspect?: boolean } } }
): Promise<SupertestReturnType<TEndpoint>> => {
const { endpoint, type } = options;
@ -27,7 +28,8 @@ export function createApmApiClient(st: supertest.SuperTest<supertest.Test>) {
const params = 'params' in options ? (options.params as Record<string, any>) : {};
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<string, string> = {
'kbn-xsrf': 'foo',

View file

@ -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);
});
});
});