[UX/Maps] Fixing APM data view id (#179257)

closes https://github.com/elastic/kibana/issues/177461

UX fix:
<img width="813" alt="Screenshot 2024-03-22 at 14 03 58"
src="48352560-d5c4-4258-8bfe-5b8dc6e4bf41">

Maps Obs layer
<img width="1325" alt="Screenshot 2024-03-22 at 16 11 31"
src="a68bbef3-a30a-45e4-987f-b472ee0db394">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cauê Marcondes 2024-03-28 12:14:32 +00:00 committed by GitHub
parent 4ffd310b92
commit aadfa5dffd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 408 additions and 175 deletions

1
.github/CODEOWNERS vendored
View file

@ -41,6 +41,7 @@ packages/analytics/shippers/elastic_v3/server @elastic/kibana-core
packages/analytics/shippers/fullstory @elastic/kibana-core
packages/kbn-apm-config-loader @elastic/kibana-core @vigneshshanmugam
x-pack/plugins/observability_solution/apm_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team
packages/kbn-apm-data-view @elastic/obs-ux-infra_services-team
x-pack/plugins/observability_solution/apm @elastic/obs-ux-infra_services-team
packages/kbn-apm-synthtrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team
packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team

View file

@ -164,6 +164,7 @@
"@kbn/analytics-shippers-fullstory": "link:packages/analytics/shippers/fullstory",
"@kbn/apm-config-loader": "link:packages/kbn-apm-config-loader",
"@kbn/apm-data-access-plugin": "link:x-pack/plugins/observability_solution/apm_data_access",
"@kbn/apm-data-view": "link:packages/kbn-apm-data-view",
"@kbn/apm-plugin": "link:x-pack/plugins/observability_solution/apm",
"@kbn/apm-utils": "link:packages/kbn-apm-utils",
"@kbn/app-link-test-plugin": "link:test/plugin_functional/plugins/app_link_test",

View file

@ -0,0 +1,17 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
const APM_STATIC_DATA_VIEW_ID_PREFIX = 'apm_static_data_view_id';
export function getStaticDataViewId(spaceId: string) {
return `${APM_STATIC_DATA_VIEW_ID_PREFIX}_${spaceId}`;
}
export function isAPMDataView(dataViewId: string) {
return dataViewId.includes(APM_STATIC_DATA_VIEW_ID_PREFIX);
}

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/apm-data-view",
"owner": "@elastic/obs-ux-infra_services-team"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/apm-data-view",
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0",
"private": true
}

View file

@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"node"
]
},
"include": [
"**/*.ts"
],
"exclude": [
"target/**/*",
]
}

View file

@ -76,6 +76,8 @@
"@kbn/apm-config-loader/*": ["packages/kbn-apm-config-loader/*"],
"@kbn/apm-data-access-plugin": ["x-pack/plugins/observability_solution/apm_data_access"],
"@kbn/apm-data-access-plugin/*": ["x-pack/plugins/observability_solution/apm_data_access/*"],
"@kbn/apm-data-view": ["packages/kbn-apm-data-view"],
"@kbn/apm-data-view/*": ["packages/kbn-apm-data-view/*"],
"@kbn/apm-plugin": ["x-pack/plugins/observability_solution/apm"],
"@kbn/apm-plugin/*": ["x-pack/plugins/observability_solution/apm/*"],
"@kbn/apm-synthtrace": ["packages/kbn-apm-synthtrace"],

View file

@ -20,6 +20,9 @@ jest.mock('../../../../../kibana_services', () => {
},
};
},
getSpaceId() {
return 'default';
},
};
});
@ -52,7 +55,7 @@ describe('createLayerDescriptor', () => {
applyGlobalQuery: true,
applyGlobalTime: true,
id: '12345',
indexPatternId: 'apm_static_index_pattern_id',
indexPatternId: 'apm_static_data_view_id_default',
metrics: [
{
field: 'transaction.duration.us',
@ -135,7 +138,7 @@ describe('createLayerDescriptor', () => {
applyGlobalTime: true,
geoField: 'client.geo.location',
id: '12345',
indexPatternId: 'apm_static_index_pattern_id',
indexPatternId: 'apm_static_data_view_id_default',
metrics: [
{
field: 'transaction.duration.us',
@ -181,7 +184,7 @@ describe('createLayerDescriptor', () => {
applyGlobalTime: true,
geoField: 'client.geo.location',
id: '12345',
indexPatternId: 'apm_static_index_pattern_id',
indexPatternId: 'apm_static_data_view_id_default',
metrics: [
{
field: 'transaction.duration.us',

View file

@ -7,6 +7,7 @@
import { v4 as uuidv4 } from 'uuid';
import { i18n } from '@kbn/i18n';
import { getStaticDataViewId } from '@kbn/apm-data-view';
import {
AggDescriptor,
ColorDynamicOptions,
@ -36,10 +37,7 @@ import { ESGeoGridSource } from '../../../../sources/es_geo_grid_source';
import { GeoJsonVectorLayer } from '../../../vector_layer';
import { HeatmapLayer } from '../../../heatmap_layer';
import { getDefaultDynamicProperties } from '../../../../styles/vector/vector_style_defaults';
// redefining APM constant to avoid making maps app depend on APM plugin
export const APM_INDEX_PATTERN_ID = 'apm_static_index_pattern_id';
export const APM_INDEX_PATTERN_TITLE = 'traces-apm*,logs-apm*,metrics-apm*,apm-*';
import { getSpaceId } from '../../../../../kibana_services';
const defaultDynamicProperties = getDefaultDynamicProperties();
@ -152,10 +150,10 @@ export function createLayerDescriptor({
if (!layer || !metric || !display) {
return null;
}
const apmSourceQuery = createAmpSourceQuery(layer);
const label = createLayerLabel(layer, metric);
const metricsDescriptor = createAggDescriptor(metric);
const apmDataViewId = getStaticDataViewId(getSpaceId());
if (display === DISPLAY.CHOROPLETH) {
const joinId = uuidv4();
@ -172,7 +170,7 @@ export function createLayerDescriptor({
right: {
type: SOURCE_TYPES.ES_TERM_SOURCE,
id: joinId,
indexPatternId: APM_INDEX_PATTERN_ID,
indexPatternId: apmDataViewId,
term: 'client.geo.country_iso_code',
metrics: [metricsDescriptor],
whereQuery: apmSourceQuery,
@ -202,7 +200,7 @@ export function createLayerDescriptor({
}
const geoGridSourceDescriptor = ESGeoGridSource.createDescriptor({
indexPatternId: APM_INDEX_PATTERN_ID,
indexPatternId: apmDataViewId,
geoField: 'client.geo.location',
metrics: [metricsDescriptor],
requestType: getGeoGridRequestType(display),

View file

@ -6,4 +6,3 @@
*/
export { ObservabilityLayerWizardConfig } from './observability_layer_wizard';
export { APM_INDEX_PATTERN_TITLE } from './create_layer_descriptor';

View file

@ -7,11 +7,11 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { getStaticDataViewId } from '@kbn/apm-data-view';
import { LAYER_WIZARD_CATEGORY, WIZARD_ID } from '../../../../../../common/constants';
import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry';
import { ObservabilityLayerTemplate } from './observability_layer_template';
import { APM_INDEX_PATTERN_ID } from './create_layer_descriptor';
import { getIndexPatternService } from '../../../../../kibana_services';
import { getIndexPatternService, getSpaceId } from '../../../../../kibana_services';
export const ObservabilityLayerWizardConfig: LayerWizard = {
id: WIZARD_ID.OBSERVABILITY,
@ -19,7 +19,7 @@ export const ObservabilityLayerWizardConfig: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH, LAYER_WIZARD_CATEGORY.SOLUTIONS],
getIsDisabled: async () => {
try {
await getIndexPatternService().get(APM_INDEX_PATTERN_ID);
await getIndexPatternService().get(getStaticDataViewId(getSpaceId()));
return false;
} catch (e) {
return true;

View file

@ -19,6 +19,9 @@ jest.mock('../../../../../kibana_services', () => {
},
};
},
getSpaceId() {
return 'default';
},
};
});
@ -30,7 +33,9 @@ import { createSecurityLayerDescriptors } from './create_layer_descriptors';
describe('createLayerDescriptor', () => {
test('apm index', () => {
expect(createSecurityLayerDescriptors('id', 'apm-*-transaction*')).toEqual([
expect(
createSecurityLayerDescriptors('apm_static_data_view_id_default', 'apm-*-transaction*')
).toEqual([
{
__dataRequests: [],
alpha: 0.75,
@ -48,7 +53,7 @@ describe('createLayerDescriptor', () => {
filterByMapBounds: true,
geoField: 'client.geo.location',
id: '12345',
indexPatternId: 'id',
indexPatternId: 'apm_static_data_view_id_default',
applyForceRefresh: true,
scalingType: 'TOP_HITS',
sortField: '',
@ -127,7 +132,7 @@ describe('createLayerDescriptor', () => {
filterByMapBounds: true,
geoField: 'server.geo.location',
id: '12345',
indexPatternId: 'id',
indexPatternId: 'apm_static_data_view_id_default',
applyForceRefresh: true,
scalingType: 'TOP_HITS',
sortField: '',
@ -205,7 +210,7 @@ describe('createLayerDescriptor', () => {
applyGlobalTime: true,
destGeoField: 'server.geo.location',
id: '12345',
indexPatternId: 'id',
indexPatternId: 'apm_static_data_view_id_default',
metrics: [
{
field: 'client.bytes',
@ -488,7 +493,9 @@ describe('createLayerDescriptor', () => {
});
test('apm data stream', () => {
expect(createSecurityLayerDescriptors('id', 'traces-apm-opbean-node')).toEqual([
expect(
createSecurityLayerDescriptors('apm_static_data_view_id_foo', 'traces-apm-opbean-node')
).toEqual([
{
__dataRequests: [],
alpha: 0.75,
@ -507,7 +514,7 @@ describe('createLayerDescriptor', () => {
geoField: 'client.geo.location',
id: '12345',
applyForceRefresh: true,
indexPatternId: 'id',
indexPatternId: 'apm_static_data_view_id_foo',
scalingType: 'TOP_HITS',
sortField: '',
sortOrder: 'desc',
@ -585,7 +592,7 @@ describe('createLayerDescriptor', () => {
filterByMapBounds: true,
geoField: 'server.geo.location',
id: '12345',
indexPatternId: 'id',
indexPatternId: 'apm_static_data_view_id_foo',
scalingType: 'TOP_HITS',
applyForceRefresh: true,
sortField: '',
@ -657,7 +664,7 @@ describe('createLayerDescriptor', () => {
applyGlobalTime: true,
destGeoField: 'server.geo.location',
id: '12345',
indexPatternId: 'id',
indexPatternId: 'apm_static_data_view_id_foo',
metrics: [
{
field: 'client.bytes',

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import minimatch from 'minimatch';
import { i18n } from '@kbn/i18n';
import { euiPaletteColorBlind } from '@elastic/eui';
import { isAPMDataView } from '@kbn/apm-data-view';
import {
LayerDescriptor,
SizeDynamicOptions,
@ -28,25 +28,16 @@ import { VectorStyle } from '../../../../styles/vector/vector_style';
import { ESSearchSource } from '../../../../sources/es_search_source';
import { ESPewPewSource } from '../../../../sources/es_pew_pew_source';
import { getDefaultDynamicProperties } from '../../../../styles/vector/vector_style_defaults';
import { APM_INDEX_PATTERN_TITLE } from '../observability';
const defaultDynamicProperties = getDefaultDynamicProperties();
const euiVisColorPalette = euiPaletteColorBlind();
function isApmIndex(indexPatternTitle: string) {
return APM_INDEX_PATTERN_TITLE.split(',')
.map((pattern) => {
return minimatch(indexPatternTitle, pattern);
})
.some(Boolean);
function getSourceField(indexPatternId: string) {
return isAPMDataView(indexPatternId) ? 'client.geo.location' : 'source.geo.location';
}
function getSourceField(indexPatternTitle: string) {
return isApmIndex(indexPatternTitle) ? 'client.geo.location' : 'source.geo.location';
}
function getDestinationField(indexPatternTitle: string) {
return isApmIndex(indexPatternTitle) ? 'server.geo.location' : 'destination.geo.location';
function getDestinationField(indexPatternId: string) {
return isAPMDataView(indexPatternId) ? 'server.geo.location' : 'destination.geo.location';
}
function createSourceLayerDescriptor(
@ -56,10 +47,10 @@ function createSourceLayerDescriptor(
) {
const sourceDescriptor = ESSearchSource.createDescriptor({
indexPatternId,
geoField: getSourceField(indexPatternTitle),
geoField: getSourceField(indexPatternId),
scalingType: SCALING_TYPES.TOP_HITS,
topHitsSplitField: isApmIndex(indexPatternTitle) ? 'client.ip' : 'source.ip',
tooltipProperties: isApmIndex(indexPatternTitle)
topHitsSplitField: isAPMDataView(indexPatternId) ? 'client.ip' : 'source.ip',
tooltipProperties: isAPMDataView(indexPatternId)
? [
'host.name',
'client.ip',
@ -114,10 +105,10 @@ function createDestinationLayerDescriptor(
) {
const sourceDescriptor = ESSearchSource.createDescriptor({
indexPatternId,
geoField: getDestinationField(indexPatternTitle),
geoField: getDestinationField(indexPatternId),
scalingType: SCALING_TYPES.TOP_HITS,
topHitsSplitField: isApmIndex(indexPatternTitle) ? 'server.ip' : 'destination.ip',
tooltipProperties: isApmIndex(indexPatternTitle)
topHitsSplitField: isAPMDataView(indexPatternId) ? 'server.ip' : 'destination.ip',
tooltipProperties: isAPMDataView(indexPatternId)
? [
'host.name',
'server.ip',
@ -172,16 +163,16 @@ function createLineLayerDescriptor(
) {
const sourceDescriptor = ESPewPewSource.createDescriptor({
indexPatternId,
sourceGeoField: getSourceField(indexPatternTitle),
destGeoField: getDestinationField(indexPatternTitle),
sourceGeoField: getSourceField(indexPatternId),
destGeoField: getDestinationField(indexPatternId),
metrics: [
{
type: AGG_TYPE.SUM,
field: isApmIndex(indexPatternTitle) ? 'client.bytes' : 'source.bytes',
field: isAPMDataView(indexPatternId) ? 'client.bytes' : 'source.bytes',
},
{
type: AGG_TYPE.SUM,
field: isApmIndex(indexPatternTitle) ? 'server.bytes' : 'destination.bytes',
field: isAPMDataView(indexPatternId) ? 'server.bytes' : 'destination.bytes',
},
],
});

View file

@ -0,0 +1,118 @@
/*
* 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 { getSecurityIndexPatterns } from './security_index_pattern_utils';
jest.mock('../../../../../kibana_services', () => {
return {
getUiSettings() {
return {
get() {
return ['logs-*', 'traces-apm*', 'endgame-*'];
},
};
},
getIndexPatternService() {
return {
getCache() {
return [
{
id: 'kibana-event-log-data-view',
type: 'index-pattern',
managed: true,
updatedAt: '2024-03-22T15:56:28.816Z',
createdAt: '2024-03-22T15:56:28.816Z',
attributes: {
title: '.kibana-event-log-*',
name: '.kibana-event-log-*',
},
references: [],
namespaces: ['default'],
version: 'WzExNCwxXQ==',
},
{
id: 'apm_static_data_view_id_default',
type: 'index-pattern',
managed: false,
updatedAt: '2024-03-21T16:50:57.705Z',
createdAt: '2024-03-21T16:50:57.705Z',
attributes: {
title: 'traces-apm*,apm-*,apm-*,metrics-apm*,apm-*',
name: 'APM',
},
references: [],
namespaces: ['default'],
version: 'WzUsMV0=',
},
{
id: 'security-solution-default',
type: 'index-pattern',
managed: false,
updatedAt: '2024-03-22T15:52:37.132Z',
createdAt: '2024-03-22T15:52:37.132Z',
attributes: {
title:
'.alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*,-*elastic-cloud-logs-*',
name: '.alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*,-*elastic-cloud-logs-*',
},
references: [],
namespaces: ['default'],
version: 'WzEwMCwxXQ==',
},
{
id: 'metrics-*',
type: 'index-pattern',
managed: true,
updatedAt: '2024-03-22T15:52:44.340Z',
createdAt: '2024-03-22T15:52:43.544Z',
attributes: {
title: 'metrics-*',
},
references: [],
namespaces: ['*', 'default'],
version: 'WzEwNiwxXQ==',
},
{
id: 'logs-*',
type: 'index-pattern',
managed: true,
updatedAt: '2024-03-22T15:52:43.600Z',
createdAt: '2024-03-22T15:52:43.544Z',
attributes: {
title: 'logs-*',
},
references: [],
namespaces: ['*', 'default'],
version: 'WzEwNSwxXQ==',
},
];
},
};
},
};
});
describe('getSecurityIndexPatterns', () => {
afterAll(() => {
jest.restoreAllMocks();
});
it('returns logs, apm and security index patterns only', async () => {
const resp = await getSecurityIndexPatterns();
expect(resp).toEqual([
{
id: 'apm_static_data_view_id_default',
title: 'traces-apm*,apm-*,apm-*,metrics-apm*,apm-*',
},
{
id: 'security-solution-default',
title:
'.alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*,-*elastic-cloud-logs-*',
},
{ id: 'logs-*', title: 'logs-*' },
]);
});
});

View file

@ -29,10 +29,11 @@ export async function getSecurityIndexPatterns(): Promise<IndexPatternMeta[]> {
const indexPatternCache = await getIndexPatternService().getCache();
return indexPatternCache!
.filter((savedObject) => {
return (securityIndexPatternTitles as string[]).some((indexPatternTitle) => {
// glob matching index pattern title
return minimatch(indexPatternTitle, savedObject?.attributes?.title);
});
return securityIndexPatternTitles.some((indexPatternTitle) =>
savedObject.attributes.title
.split(',')
.some((pattern) => minimatch(indexPatternTitle, pattern))
);
})
.map((savedObject) => {
return {

View file

@ -33,6 +33,12 @@ export function setIsCloudEnabled(enabled: boolean) {
}
export const getIsCloud = () => isCloudEnabled;
let spaceId = 'default';
export const getSpaceId = () => spaceId;
export const setSpaceId = (_spaceId: string) => {
spaceId = _spaceId;
};
export const getIndexNameFormComponent = () => pluginsStart.fileUpload.IndexNameFormComponent;
export const getFileUploadComponent = () => pluginsStart.fileUpload.FileUploadComponent;
export const getIndexPatternService = () => pluginsStart.data.dataViews;

View file

@ -81,7 +81,12 @@ import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_a
import { APP_NAME, APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
import { getMapsVisTypeAlias } from './maps_vis_type_alias';
import { featureCatalogueEntry } from './feature_catalogue_entry';
import { setIsCloudEnabled, setMapAppConfig, setStartServices } from './kibana_services';
import {
setIsCloudEnabled,
setMapAppConfig,
setSpaceId,
setStartServices,
} from './kibana_services';
import { MapInspectorView } from './inspector/map_adapter/map_inspector_view';
import { VectorTileInspectorView } from './inspector/vector_tile_adapter/vector_tile_inspector_view';
@ -204,9 +209,13 @@ export class MapsPlugin
euiIconType: APP_ICON_SOLUTION,
category: DEFAULT_APP_CATEGORIES.kibana,
async mount(params: AppMountParameters) {
const [coreStart, { savedObjectsTagging }] = await core.getStartServices();
const [coreStart, { savedObjectsTagging, spaces }] = await core.getStartServices();
const UsageTracker =
plugins.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
const activeSpace = await spaces?.getActiveSpace();
if (activeSpace) {
setSpaceId(activeSpace.id);
}
const { renderApp } = await import('./render_app');
return renderApp(params, { coreStart, AppUsageTracker: UsageTracker, savedObjectsTagging });
},

View file

@ -87,6 +87,7 @@
"@kbn/presentation-publishing",
"@kbn/saved-objects-finder-plugin",
"@kbn/esql-utils",
"@kbn/apm-data-view"
],
"exclude": [
"target/**/*",

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
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

@ -80,6 +80,8 @@ async function saveApmIndices({
});
clearCache();
// Reload window to update APM data view with new indices
window.location.reload();
}
type ApiResponse =

View file

@ -12,7 +12,7 @@ import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_ev
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';
import { createOrUpdateStaticDataView } from './create_static_data_view';
function getMockedDataViewService(existingDataViewTitle: string) {
return {
@ -55,7 +55,7 @@ const apmEventClientMock = {
describe('createStaticDataView', () => {
it(`should not create data view if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => {
const dataViewService = getMockedDataViewService('apm-*');
await createStaticDataView({
await createOrUpdateStaticDataView({
apmEventClient: apmEventClientMock,
resources: {
config: { autoCreateApmDataView: false },
@ -75,7 +75,7 @@ describe('createStaticDataView', () => {
const dataViewService = getMockedDataViewService('apm-*');
await createStaticDataView({
await createOrUpdateStaticDataView({
apmEventClient: apmEventClientMock,
resources: {
config: { autoCreateApmDataView: false },
@ -95,7 +95,7 @@ describe('createStaticDataView', () => {
const dataViewService = getMockedDataViewService('apm-*');
await createStaticDataView({
await createOrUpdateStaticDataView({
apmEventClient: apmEventClientMock,
resources: {
core: coreMock,
@ -119,7 +119,7 @@ describe('createStaticDataView', () => {
const expectedDataViewTitle =
'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*';
await createStaticDataView({
await createOrUpdateStaticDataView({
apmEventClient: apmEventClientMock,
resources: {
core: coreMock,
@ -150,7 +150,7 @@ describe('createStaticDataView', () => {
'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*'
);
await createStaticDataView({
await createOrUpdateStaticDataView({
apmEventClient: apmEventClientMock,
resources: {
core: coreMock,

View file

@ -8,10 +8,7 @@
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 { getStaticDataViewId } from '@kbn/apm-data-view';
import {
TRACE_ID,
TRANSACTION_ID,
@ -28,7 +25,7 @@ export type CreateDataViewResponse = Promise<
| { created: boolean; reason?: string }
>;
export async function createStaticDataView({
export async function createOrUpdateStaticDataView({
dataViewService,
resources,
apmEventClient,
@ -42,7 +39,7 @@ export async function createStaticDataView({
logger: Logger;
}): CreateDataViewResponse {
const { config } = resources;
const dataViewId = getDataViewId(spaceId);
const dataViewId = getStaticDataViewId(spaceId);
logger.info(`create static data view ${dataViewId}`);
return withApmSpan('create_static_data_view', async () => {
@ -100,15 +97,6 @@ export async function createStaticDataView({
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 };
});
}

View file

@ -8,7 +8,7 @@
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import {
CreateDataViewResponse,
createStaticDataView,
createOrUpdateStaticDataView,
} from './create_static_data_view';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { getApmDataViewIndexPattern } from './get_apm_data_view_index_pattern';
@ -39,7 +39,7 @@ const staticDataViewRoute = createApmServerRoute({
true
);
const res = await createStaticDataView({
const res = await createOrUpdateStaticDataView({
dataViewService,
resources,
apmEventClient,

View file

@ -65,6 +65,7 @@
"@kbn/config-schema",
"@kbn/repo-info",
"@kbn/apm-utils",
"@kbn/apm-data-view",
"@kbn/logging",
"@kbn/std",
"@kbn/core-saved-objects-api-server-mocks",

View file

@ -29,7 +29,8 @@
"observability",
"security",
"maps",
"lens"
"lens",
"spaces"
],
"requiredBundles": [
"kibanaReact",

View file

@ -119,12 +119,14 @@ export function UXAppRoot({
lens,
},
isDev,
spaceId,
}: {
appMountParameters: AppMountParameters;
core: CoreStart;
deps: ApmPluginSetupDeps;
corePlugins: ApmPluginStartDeps;
isDev: boolean;
spaceId: string;
}) {
const { history } = appMountParameters;
const i18nCore = core.i18n;
@ -168,6 +170,7 @@ export function UXAppRoot({
appMountParameters,
exploratoryView,
observabilityShared,
spaceId,
}}
>
<i18nCore.Context>
@ -207,12 +210,14 @@ export const renderApp = ({
appMountParameters,
corePlugins,
isDev,
spaceId,
}: {
core: CoreStart;
deps: ApmPluginSetupDeps;
appMountParameters: AppMountParameters;
corePlugins: ApmPluginStartDeps;
isDev: boolean;
spaceId: string;
}) => {
const { element } = appMountParameters;
@ -231,6 +236,7 @@ export const renderApp = ({
deps={deps}
corePlugins={corePlugins}
isDev={isDev}
spaceId={spaceId}
/>,
element
);

View file

@ -29,7 +29,7 @@ export const mockLayerList = [
label: 'Page load duration',
},
],
indexPatternId: 'apm_static_index_pattern_id',
indexPatternId: 'apm_static_data_view_id_default',
},
},
],
@ -101,7 +101,7 @@ export const mockLayerList = [
'transaction.type : "page-load" and service.name : "undefined"',
},
metrics: [{ type: 'avg', field: 'transaction.duration.us' }],
indexPatternId: 'apm_static_index_pattern_id',
indexPatternId: 'apm_static_data_view_id_default',
},
},
],

View file

@ -9,7 +9,14 @@ import { renderHook } from '@testing-library/react-hooks';
import { mockLayerList } from './__mocks__/regions_layer.mock';
import { useLayerList } from './use_layer_list';
jest.mock('../../../../context/use_ux_plugin_context', () => {
return { useUxPluginContext: () => ({ spaceId: 'default' }) };
});
describe('useLayerList', () => {
afterAll(() => {
jest.resetAllMocks();
});
test('it returns the region layer', () => {
const { result } = renderHook(() => useLayerList());
expect(result.current).toStrictEqual(mockLayerList);

View file

@ -5,62 +5,32 @@
* 2.0.
*/
import { getStaticDataViewId } from '@kbn/apm-data-view';
import {
EMSFileSourceDescriptor,
ESTermSourceDescriptor,
LayerDescriptor as BaseLayerDescriptor,
VectorLayerDescriptor as BaseVectorLayerDescriptor,
VectorStyleDescriptor,
AGG_TYPE,
COLOR_MAP_TYPE,
EMSFileSourceDescriptor,
ESTermSourceDescriptor,
FIELD_ORIGIN,
LABEL_BORDER_SIZES,
LABEL_POSITIONS,
LayerDescriptor as BaseLayerDescriptor,
LAYER_TYPE,
SOURCE_TYPES,
STYLE_TYPE,
SYMBOLIZE_AS_TYPES,
VectorLayerDescriptor as BaseVectorLayerDescriptor,
VectorStyleDescriptor,
} from '@kbn/maps-plugin/common';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { useMemo } from 'react';
import {
SERVICE_NAME,
TRANSACTION_TYPE,
} from '../../../../../common/elasticsearch_fieldnames';
import { TRANSACTION_PAGE_LOAD } from '../../../../../common/transaction_types';
import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants';
const ES_TERM_SOURCE_COUNTRY: ESTermSourceDescriptor = {
type: SOURCE_TYPES.ES_TERM_SOURCE,
id: '3657625d-17b0-41ef-99ba-3a2b2938655c',
term: 'client.geo.country_iso_code',
metrics: [
{
type: AGG_TYPE.AVG,
field: 'transaction.duration.us',
label: 'Page load duration',
},
],
indexPatternId: APM_STATIC_INDEX_PATTERN_ID,
applyGlobalQuery: true,
applyGlobalTime: true,
applyForceRefresh: true,
};
const ES_TERM_SOURCE_REGION: ESTermSourceDescriptor = {
type: SOURCE_TYPES.ES_TERM_SOURCE,
id: 'e62a1b9c-d7ff-4fd4-a0f6-0fdc44bb9e41',
term: 'client.geo.region_iso_code',
metrics: [{ type: AGG_TYPE.AVG, field: 'transaction.duration.us' }],
whereQuery: {
query: 'transaction.type : "page-load"',
language: 'kuery',
},
indexPatternId: APM_STATIC_INDEX_PATTERN_ID,
applyGlobalQuery: true,
applyGlobalTime: true,
applyForceRefresh: true,
};
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { useUxPluginContext } from '../../../../context/use_ux_plugin_context';
const getWhereQuery = (serviceName: string) => {
return {
@ -84,10 +54,49 @@ interface VectorLayerDescriptor extends BaseVectorLayerDescriptor {
export function useLayerList() {
const { urlParams } = useLegacyUrlParams();
const { spaceId } = useUxPluginContext();
const { esTermSourceCountry, esTermSourceRegion } = useMemo(() => {
const _esTermSourceCountry: ESTermSourceDescriptor = {
type: SOURCE_TYPES.ES_TERM_SOURCE,
id: '3657625d-17b0-41ef-99ba-3a2b2938655c',
term: 'client.geo.country_iso_code',
metrics: [
{
type: AGG_TYPE.AVG,
field: 'transaction.duration.us',
label: 'Page load duration',
},
],
indexPatternId: getStaticDataViewId(spaceId),
applyGlobalQuery: true,
applyGlobalTime: true,
applyForceRefresh: true,
};
const _esTermSourceRegion: ESTermSourceDescriptor = {
type: SOURCE_TYPES.ES_TERM_SOURCE,
id: 'e62a1b9c-d7ff-4fd4-a0f6-0fdc44bb9e41',
term: 'client.geo.region_iso_code',
metrics: [{ type: AGG_TYPE.AVG, field: 'transaction.duration.us' }],
whereQuery: {
query: 'transaction.type : "page-load"',
language: 'kuery',
},
indexPatternId: getStaticDataViewId(spaceId),
applyGlobalQuery: true,
applyGlobalTime: true,
applyForceRefresh: true,
};
return {
esTermSourceCountry: _esTermSourceCountry,
esTermSourceRegion: _esTermSourceRegion,
};
}, [spaceId]);
const { serviceName } = urlParams;
ES_TERM_SOURCE_COUNTRY.whereQuery = getWhereQuery(serviceName!);
esTermSourceCountry.whereQuery = getWhereQuery(serviceName!);
const getLayerStyle = (fieldName: string): VectorStyleDescriptor => {
return {
@ -151,7 +160,7 @@ export function useLayerList() {
joins: [
{
leftField: 'iso2',
right: ES_TERM_SOURCE_COUNTRY,
right: esTermSourceCountry,
},
],
sourceDescriptor: {
@ -169,13 +178,13 @@ export function useLayerList() {
type: LAYER_TYPE.GEOJSON_VECTOR,
};
ES_TERM_SOURCE_REGION.whereQuery = getWhereQuery(serviceName!);
esTermSourceRegion.whereQuery = getWhereQuery(serviceName!);
const pageLoadDurationByAdminRegionLayer: VectorLayerDescriptor = {
joins: [
{
leftField: 'region_iso_code',
right: ES_TERM_SOURCE_REGION,
right: esTermSourceRegion,
},
],
sourceDescriptor: {

View file

@ -7,8 +7,8 @@
import { useMemo } from 'react';
import { FieldFilter as Filter } from '@kbn/es-query';
import { getStaticDataViewId } from '@kbn/apm-data-view';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants';
import {
CLIENT_GEO_COUNTRY_ISO_CODE,
SERVICE_NAME,
@ -17,11 +17,16 @@ import {
USER_AGENT_NAME,
USER_AGENT_OS,
} from '../../../../../common/elasticsearch_fieldnames';
import { useUxPluginContext } from '../../../../context/use_ux_plugin_context';
const getWildcardFilter = (field: string, value: string): Filter => {
const getWildcardFilter = (
field: string,
value: string,
spaceId: string
): Filter => {
return {
meta: {
index: APM_STATIC_INDEX_PATTERN_ID,
index: getStaticDataViewId(spaceId),
alias: null,
negate: false,
disabled: false,
@ -33,10 +38,14 @@ const getWildcardFilter = (field: string, value: string): Filter => {
};
};
const getMatchFilter = (field: string, value: string): Filter => {
const getMatchFilter = (
field: string,
value: string,
spaceId: string
): Filter => {
return {
meta: {
index: APM_STATIC_INDEX_PATTERN_ID,
index: getStaticDataViewId(spaceId),
alias: null,
negate: false,
disabled: false,
@ -51,11 +60,12 @@ const getMatchFilter = (field: string, value: string): Filter => {
const getMultiMatchFilter = (
field: string,
values: string[],
spaceId: string,
negate = false
): Filter => {
return {
meta: {
index: APM_STATIC_INDEX_PATTERN_ID,
index: getStaticDataViewId(spaceId),
type: 'phrases',
key: field,
value: values.join(', '),
@ -73,25 +83,28 @@ const getMultiMatchFilter = (
};
};
const existFilter: Filter = {
meta: {
index: APM_STATIC_INDEX_PATTERN_ID,
alias: null,
negate: false,
disabled: false,
type: 'exists',
key: 'transaction.marks.navigationTiming.fetchStart',
value: 'exists',
},
query: {
exists: {
field: 'transaction.marks.navigationTiming.fetchStart',
function getExistFilter(spaceId: string): Filter {
return {
meta: {
index: getStaticDataViewId(spaceId),
alias: null,
negate: false,
disabled: false,
type: 'exists',
key: 'transaction.marks.navigationTiming.fetchStart',
value: 'exists',
},
},
};
query: {
exists: {
field: 'transaction.marks.navigationTiming.fetchStart',
},
},
};
}
export const useMapFilters = (): Filter[] => {
const { urlParams, uxUiFilters } = useLegacyUrlParams();
const { spaceId } = useUxPluginContext();
const { serviceName, searchTerm } = urlParams;
@ -109,48 +122,66 @@ export const useMapFilters = (): Filter[] => {
} = uxUiFilters;
return useMemo(() => {
const filters: Filter[] = [existFilter];
const filters: Filter[] = [getExistFilter(spaceId)];
if (serviceName) {
filters.push(getMatchFilter(SERVICE_NAME, serviceName));
filters.push(getMatchFilter(SERVICE_NAME, serviceName, spaceId));
}
if (browser) {
filters.push(getMultiMatchFilter(USER_AGENT_NAME, browser));
filters.push(getMultiMatchFilter(USER_AGENT_NAME, browser, spaceId));
}
if (device) {
filters.push(getMultiMatchFilter(USER_AGENT_DEVICE, device));
filters.push(getMultiMatchFilter(USER_AGENT_DEVICE, device, spaceId));
}
if (os) {
filters.push(getMultiMatchFilter(USER_AGENT_OS, os));
filters.push(getMultiMatchFilter(USER_AGENT_OS, os, spaceId));
}
if (location) {
filters.push(getMultiMatchFilter(CLIENT_GEO_COUNTRY_ISO_CODE, location));
filters.push(
getMultiMatchFilter(CLIENT_GEO_COUNTRY_ISO_CODE, location, spaceId)
);
}
if (transactionUrl) {
filters.push(getMultiMatchFilter(TRANSACTION_URL, transactionUrl));
filters.push(
getMultiMatchFilter(TRANSACTION_URL, transactionUrl, spaceId)
);
}
if (browserExcluded) {
filters.push(getMultiMatchFilter(USER_AGENT_NAME, browserExcluded, true));
filters.push(
getMultiMatchFilter(USER_AGENT_NAME, browserExcluded, spaceId, true)
);
}
if (deviceExcluded) {
filters.push(
getMultiMatchFilter(USER_AGENT_DEVICE, deviceExcluded, true)
getMultiMatchFilter(USER_AGENT_DEVICE, deviceExcluded, spaceId, true)
);
}
if (osExcluded) {
filters.push(getMultiMatchFilter(USER_AGENT_OS, osExcluded, true));
filters.push(
getMultiMatchFilter(USER_AGENT_OS, osExcluded, spaceId, true)
);
}
if (locationExcluded) {
filters.push(
getMultiMatchFilter(CLIENT_GEO_COUNTRY_ISO_CODE, locationExcluded, true)
getMultiMatchFilter(
CLIENT_GEO_COUNTRY_ISO_CODE,
locationExcluded,
spaceId,
true
)
);
}
if (transactionUrlExcluded) {
filters.push(
getMultiMatchFilter(TRANSACTION_URL, transactionUrlExcluded, true)
getMultiMatchFilter(
TRANSACTION_URL,
transactionUrlExcluded,
spaceId,
true
)
);
}
if (searchTerm) {
filters.push(getWildcardFilter(TRANSACTION_URL, searchTerm));
filters.push(getWildcardFilter(TRANSACTION_URL, searchTerm, spaceId));
}
return filters;
@ -167,5 +198,6 @@ export const useMapFilters = (): Filter[] => {
locationExcluded,
transactionUrlExcluded,
searchTerm,
spaceId,
]);
};

View file

@ -14,6 +14,7 @@ export interface PluginContextValue {
appMountParameters: AppMountParameters;
exploratoryView: ExploratoryViewPublicStart;
observabilityShared: ObservabilitySharedPluginStart;
spaceId: string;
}
export const PluginContext = createContext({} as PluginContextValue);

View file

@ -5,4 +5,9 @@
* 2.0.
*/
export const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id';
import { useContext } from 'react';
import { PluginContext } from './plugin_context';
export function useUxPluginContext() {
return useContext(PluginContext);
}

View file

@ -47,6 +47,7 @@ import {
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
} from '@kbn/observability-ai-assistant-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
export type UxPluginSetup = void;
export type UxPluginStart = void;
@ -75,6 +76,7 @@ export interface ApmPluginStartDeps {
exploratoryView: ExploratoryViewPublicStart;
dataViews: DataViewsPublicPluginStart;
lens: LensPublicStart;
spaces?: SpacesPluginStart;
}
async function getDataStartPlugin(core: CoreSetup) {
@ -201,12 +203,17 @@ export class UxPlugin implements Plugin<UxPluginSetup, UxPluginStart> {
core.getStartServices(),
]);
const activeSpace = await (
corePlugins as ApmPluginStartDeps
).spaces?.getActiveSpace();
return renderApp({
isDev,
core: coreStart,
deps: pluginSetupDeps,
appMountParameters,
corePlugins: corePlugins as ApmPluginStartDeps,
spaceId: activeSpace?.id || 'default',
});
},
});

View file

@ -42,7 +42,9 @@
"@kbn/shared-ux-router",
"@kbn/observability-ai-assistant-plugin",
"@kbn/config-schema",
"@kbn/shared-ux-link-redirect-app"
"@kbn/shared-ux-link-redirect-app",
"@kbn/apm-data-view",
"@kbn/spaces-plugin",
],
"exclude": ["target/**/*"]
}

View file

@ -8,10 +8,10 @@
import { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import expect from '@kbn/expect';
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';
import { getStaticDataViewId } from '@kbn/apm-data-view';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { SupertestReturnType, ApmApiError } from '../../common/apm_api_supertest';
@ -39,14 +39,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
function deleteDataView(spaceId: string) {
return supertest
.delete(`/s/${spaceId}/api/saved_objects/index-pattern/${getDataViewId(spaceId)}?force=true`)
.delete(
`/s/${spaceId}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}?force=true`
)
.set('kbn-xsrf', 'foo');
}
function getDataView({ spaceId }: { spaceId: string }) {
const spacePrefix = spaceId !== 'default' ? `/s/${spaceId}` : '';
return supertest.get(
`${spacePrefix}/api/saved_objects/index-pattern/${getDataViewId(spaceId)}`
`${spacePrefix}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}`
);
}

View file

@ -170,5 +170,6 @@
"@kbn/slo-plugin",
"@kbn/aiops-test-utils",
"@kbn/observability-ai-assistant-app-plugin",
"@kbn/apm-data-view",
]
}

View file

@ -3198,6 +3198,10 @@
version "0.0.0"
uid ""
"@kbn/apm-data-view@link:packages/kbn-apm-data-view":
version "0.0.0"
uid ""
"@kbn/apm-plugin@link:x-pack/plugins/observability_solution/apm":
version "0.0.0"
uid ""