mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] observability real user monitoring solution layer (#64949)
* [Maps] observability real user monitoring solution layer * make stateful component * rename RUM to observability * layer select * metrics select * display select * clean up * create cholopleth map layer descriptor * get styling working * fix unique count agg * create geo grid source * render as heatmap * clusters * tslint errors * last tslint fix * only show card when APM index exists * layer label * clean up * fix getLayerWizards * add parans * review feedback * add cluster labels * use green to red ramp * tslint fix Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
415d33b756
commit
891e27eb1f
26 changed files with 1077 additions and 83 deletions
|
@ -57,6 +57,7 @@ export enum SOURCE_TYPES {
|
|||
ES_GEO_GRID = 'ES_GEO_GRID',
|
||||
ES_SEARCH = 'ES_SEARCH',
|
||||
ES_PEW_PEW = 'ES_PEW_PEW',
|
||||
ES_TERM_SOURCE = 'ES_TERM_SOURCE',
|
||||
EMS_XYZ = 'EMS_XYZ', // identifies a custom TMS source. Name is a little unfortunate.
|
||||
WMS = 'WMS',
|
||||
KIBANA_TILEMAP = 'KIBANA_TILEMAP',
|
||||
|
|
|
@ -71,6 +71,7 @@ export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
|
|||
export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & {
|
||||
indexPatternTitle: string;
|
||||
term: string; // term field name
|
||||
whereQuery?: Query;
|
||||
};
|
||||
|
||||
export type KibanaRegionmapSourceDescriptor = AbstractSourceDescriptor & {
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AGG_DELIMITER, AGG_TYPE, JOIN_FIELD_NAME_PREFIX } from './constants';
|
||||
import { AGG_DELIMITER, AGG_TYPE, COUNT_PROP_NAME, JOIN_FIELD_NAME_PREFIX } from './constants';
|
||||
|
||||
// function in common since its needed by migration
|
||||
export function getJoinAggKey({
|
||||
aggType,
|
||||
aggFieldName,
|
||||
|
@ -15,8 +14,18 @@ export function getJoinAggKey({
|
|||
aggType: AGG_TYPE;
|
||||
aggFieldName?: string;
|
||||
rightSourceId: string;
|
||||
}) {
|
||||
}): string {
|
||||
const metricKey =
|
||||
aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${aggFieldName}` : aggType;
|
||||
return `${JOIN_FIELD_NAME_PREFIX}${metricKey}__${rightSourceId}`;
|
||||
}
|
||||
|
||||
export function getSourceAggKey({
|
||||
aggType,
|
||||
aggFieldName,
|
||||
}: {
|
||||
aggType: AGG_TYPE;
|
||||
aggFieldName?: string;
|
||||
}): string {
|
||||
return aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${aggFieldName}` : COUNT_PROP_NAME;
|
||||
}
|
|
@ -13,7 +13,7 @@ import {
|
|||
LAYER_TYPE,
|
||||
VECTOR_STYLES,
|
||||
} from '../constants';
|
||||
import { getJoinAggKey } from '../get_join_key';
|
||||
import { getJoinAggKey } from '../get_agg_key';
|
||||
import {
|
||||
AggDescriptor,
|
||||
JoinDescriptor,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import 'gis_map/gis_map';
|
||||
@import 'layer_addpanel/source_select/index';
|
||||
@import 'layer_addpanel/index';
|
||||
@import 'layer_panel/index';
|
||||
@import 'widget_overlay/index';
|
||||
@import 'toolbar_overlay/index';
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui';
|
||||
import { getLayerWizards, LayerWizard } from '../../layers/layer_wizard_registry';
|
||||
|
||||
interface Props {
|
||||
onSelect: (layerWizard: LayerWizard) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
layerWizards: LayerWizard[];
|
||||
}
|
||||
|
||||
export class LayerWizardSelect extends Component<Props, State> {
|
||||
private _isMounted: boolean = false;
|
||||
|
||||
state = {
|
||||
layerWizards: [],
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadLayerWizards();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
async _loadLayerWizards() {
|
||||
const layerWizards = await getLayerWizards();
|
||||
if (this._isMounted) {
|
||||
this.setState({ layerWizards });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state.layerWizards.map((layerWizard: LayerWizard) => {
|
||||
const icon = layerWizard.icon ? <EuiIcon type={layerWizard.icon} size="l" /> : undefined;
|
||||
|
||||
const onClick = () => {
|
||||
this.props.onSelect(layerWizard);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment key={layerWizard.title}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCard
|
||||
className="mapLayerAddpanel__card"
|
||||
title={layerWizard.title}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
description={layerWizard.description}
|
||||
layout="horizontal"
|
||||
data-test-subj={_.camelCase(layerWizard.title)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import 'source_select';
|
|
@ -1,41 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { getLayerWizards } from '../../../layers/layer_wizard_registry';
|
||||
import { EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function SourceSelect({ updateSourceSelection }) {
|
||||
const sourceCards = getLayerWizards().map(layerWizard => {
|
||||
const icon = layerWizard.icon ? <EuiIcon type={layerWizard.icon} size="l" /> : null;
|
||||
|
||||
const onClick = () => {
|
||||
updateSourceSelection({
|
||||
layerWizard: layerWizard,
|
||||
isIndexingSource: !!layerWizard.isIndexingSource,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment key={layerWizard.title}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCard
|
||||
className="mapLayerAddpanel__card"
|
||||
title={layerWizard.title}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
description={layerWizard.description}
|
||||
layout="horizontal"
|
||||
data-test-subj={_.camelCase(layerWizard.title)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return <Fragment>{sourceCards}</Fragment>;
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { SourceSelect } from './source_select/source_select';
|
||||
import { LayerWizardSelect } from './layer_wizard_select';
|
||||
import { FlyoutFooter } from './flyout_footer';
|
||||
import { ImportEditor } from './import_editor';
|
||||
import { EuiButtonEmpty, EuiPanel, EuiTitle, EuiFlyoutHeader, EuiSpacer } from '@elastic/eui';
|
||||
|
@ -80,8 +80,8 @@ export class AddLayerPanel extends Component {
|
|||
this.props.removeTransientLayer();
|
||||
};
|
||||
|
||||
_onSourceSelectionChange = ({ layerWizard, isIndexingSource }) => {
|
||||
this.setState({ layerWizard, importView: isIndexingSource });
|
||||
_onWizardSelect = layerWizard => {
|
||||
this.setState({ layerWizard, importView: !!layerWizard.isIndexingSource });
|
||||
};
|
||||
|
||||
_layerAddHandler = () => {
|
||||
|
@ -100,7 +100,7 @@ export class AddLayerPanel extends Component {
|
|||
|
||||
_renderPanelBody() {
|
||||
if (!this.state.layerWizard) {
|
||||
return <SourceSelect updateSourceSelection={this._onSourceSelectionChange} />;
|
||||
return <LayerWizardSelect onSelect={this._onWizardSelect} />;
|
||||
}
|
||||
|
||||
const backButton = this.props.isIndexingTriggered ? null : (
|
||||
|
|
|
@ -20,7 +20,7 @@ export type RenderWizardArguments = {
|
|||
};
|
||||
|
||||
export type LayerWizard = {
|
||||
checkVisibility?: () => boolean;
|
||||
checkVisibility?: () => Promise<boolean>;
|
||||
description: string;
|
||||
icon: string;
|
||||
isIndexingSource?: boolean;
|
||||
|
@ -31,11 +31,23 @@ export type LayerWizard = {
|
|||
const registry: LayerWizard[] = [];
|
||||
|
||||
export function registerLayerWizard(layerWizard: LayerWizard) {
|
||||
registry.push(layerWizard);
|
||||
}
|
||||
|
||||
export function getLayerWizards(): LayerWizard[] {
|
||||
return registry.filter(layerWizard => {
|
||||
return layerWizard.checkVisibility ? layerWizard.checkVisibility() : true;
|
||||
registry.push({
|
||||
checkVisibility: async () => {
|
||||
return true;
|
||||
},
|
||||
...layerWizard,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getLayerWizards(): Promise<LayerWizard[]> {
|
||||
const promises = registry.map(async layerWizard => {
|
||||
return {
|
||||
...layerWizard,
|
||||
// @ts-ignore
|
||||
isVisible: await layerWizard.checkVisibility(),
|
||||
};
|
||||
});
|
||||
return (await Promise.all(promises)).filter(({ isVisible }) => {
|
||||
return isVisible;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,17 +25,19 @@ import { tmsLayerWizardConfig } from './sources/xyz_tms_source';
|
|||
// @ts-ignore
|
||||
import { wmsLayerWizardConfig } from './sources/wms_source';
|
||||
import { mvtVectorSourceWizardConfig } from './sources/mvt_single_layer_vector_source';
|
||||
// @ts-ignore
|
||||
import { ObservabilityLayerWizardConfig } from './solution_layers/observability';
|
||||
import { getInjectedVarFunc } from '../kibana_services';
|
||||
|
||||
// Registration order determines display order
|
||||
let registered = false;
|
||||
export function registerLayerWizards() {
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Registration order determines display order
|
||||
// @ts-ignore
|
||||
registerLayerWizard(uploadLayerWizardConfig);
|
||||
registerLayerWizard(ObservabilityLayerWizardConfig);
|
||||
// @ts-ignore
|
||||
registerLayerWizard(esDocumentsLayerWizardConfig);
|
||||
// @ts-ignore
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../../../kibana_services', () => {
|
||||
const mockUiSettings = {
|
||||
get: () => {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
return {
|
||||
getUiSettings: () => {
|
||||
return mockUiSettings;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('uuid/v4', () => {
|
||||
return function() {
|
||||
return '12345';
|
||||
};
|
||||
});
|
||||
|
||||
import { createLayerDescriptor } from './create_layer_descriptor';
|
||||
import { OBSERVABILITY_LAYER_TYPE } from './layer_select';
|
||||
import { OBSERVABILITY_METRIC_TYPE } from './metric_select';
|
||||
import { DISPLAY } from './display_select';
|
||||
|
||||
describe('createLayerDescriptor', () => {
|
||||
test('Should create vector layer descriptor with join when displayed as choropleth', () => {
|
||||
const layerDescriptor = createLayerDescriptor({
|
||||
layer: OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE,
|
||||
metric: OBSERVABILITY_METRIC_TYPE.TRANSACTION_DURATION,
|
||||
display: DISPLAY.CHOROPLETH,
|
||||
});
|
||||
|
||||
expect(layerDescriptor).toEqual({
|
||||
__dataRequests: [],
|
||||
alpha: 0.75,
|
||||
id: '12345',
|
||||
joins: [
|
||||
{
|
||||
leftField: 'iso2',
|
||||
right: {
|
||||
id: '12345',
|
||||
indexPatternId: 'apm_static_index_pattern_id',
|
||||
indexPatternTitle: 'apm-*',
|
||||
metrics: [
|
||||
{
|
||||
field: 'transaction.duration.us',
|
||||
type: 'avg',
|
||||
},
|
||||
],
|
||||
term: 'client.geo.country_iso_code',
|
||||
type: 'ES_TERM_SOURCE',
|
||||
whereQuery: {
|
||||
language: 'kuery',
|
||||
query: 'processor.event:"transaction"',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
label: '[Performance] Duration',
|
||||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
id: 'world_countries',
|
||||
tooltipProperties: ['name', 'iso2'],
|
||||
type: 'EMS_FILE',
|
||||
},
|
||||
style: {
|
||||
isTimeAware: true,
|
||||
properties: {
|
||||
fillColor: {
|
||||
options: {
|
||||
color: 'Green to Red',
|
||||
colorCategory: 'palette_0',
|
||||
field: {
|
||||
name: '__kbnjoin__avg_of_transaction.duration.us__12345',
|
||||
origin: 'join',
|
||||
},
|
||||
fieldMetaOptions: {
|
||||
isEnabled: true,
|
||||
sigma: 3,
|
||||
},
|
||||
type: 'ORDINAL',
|
||||
},
|
||||
type: 'DYNAMIC',
|
||||
},
|
||||
icon: {
|
||||
options: {
|
||||
value: 'marker',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
iconOrientation: {
|
||||
options: {
|
||||
orientation: 0,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
iconSize: {
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelText: {
|
||||
options: {
|
||||
value: '',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelBorderColor: {
|
||||
options: {
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelBorderSize: {
|
||||
options: {
|
||||
size: 'SMALL',
|
||||
},
|
||||
},
|
||||
labelColor: {
|
||||
options: {
|
||||
color: '#000000',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelSize: {
|
||||
options: {
|
||||
size: 14,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
lineColor: {
|
||||
options: {
|
||||
color: '#3d3d3d',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
lineWidth: {
|
||||
options: {
|
||||
size: 1,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
symbolizeAs: {
|
||||
options: {
|
||||
value: 'circle',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
},
|
||||
type: 'VECTOR',
|
||||
visible: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('Should create heatmap layer descriptor when displayed as heatmap', () => {
|
||||
const layerDescriptor = createLayerDescriptor({
|
||||
layer: OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE,
|
||||
metric: OBSERVABILITY_METRIC_TYPE.TRANSACTION_DURATION,
|
||||
display: DISPLAY.HEATMAP,
|
||||
});
|
||||
expect(layerDescriptor).toEqual({
|
||||
__dataRequests: [],
|
||||
alpha: 0.75,
|
||||
id: '12345',
|
||||
joins: [],
|
||||
label: '[Performance] Duration',
|
||||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: 'processor.event:"transaction"',
|
||||
},
|
||||
sourceDescriptor: {
|
||||
geoField: 'client.geo.location',
|
||||
id: '12345',
|
||||
indexPatternId: 'apm_static_index_pattern_id',
|
||||
metrics: [
|
||||
{
|
||||
field: 'transaction.duration.us',
|
||||
type: 'avg',
|
||||
},
|
||||
],
|
||||
requestType: 'heatmap',
|
||||
resolution: 'MOST_FINE',
|
||||
type: 'ES_GEO_GRID',
|
||||
},
|
||||
style: {
|
||||
colorRampName: 'theclassic',
|
||||
type: 'HEATMAP',
|
||||
},
|
||||
type: 'HEATMAP',
|
||||
visible: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('Should create vector layer descriptor when displayed as clusters', () => {
|
||||
const layerDescriptor = createLayerDescriptor({
|
||||
layer: OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE,
|
||||
metric: OBSERVABILITY_METRIC_TYPE.TRANSACTION_DURATION,
|
||||
display: DISPLAY.CLUSTERS,
|
||||
});
|
||||
expect(layerDescriptor).toEqual({
|
||||
__dataRequests: [],
|
||||
alpha: 0.75,
|
||||
id: '12345',
|
||||
joins: [],
|
||||
label: '[Performance] Duration',
|
||||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: 'processor.event:"transaction"',
|
||||
},
|
||||
sourceDescriptor: {
|
||||
geoField: 'client.geo.location',
|
||||
id: '12345',
|
||||
indexPatternId: 'apm_static_index_pattern_id',
|
||||
metrics: [
|
||||
{
|
||||
field: 'transaction.duration.us',
|
||||
type: 'avg',
|
||||
},
|
||||
],
|
||||
requestType: 'point',
|
||||
resolution: 'MOST_FINE',
|
||||
type: 'ES_GEO_GRID',
|
||||
},
|
||||
style: {
|
||||
isTimeAware: true,
|
||||
properties: {
|
||||
fillColor: {
|
||||
options: {
|
||||
color: 'Green to Red',
|
||||
colorCategory: 'palette_0',
|
||||
field: {
|
||||
name: 'avg_of_transaction.duration.us',
|
||||
origin: 'source',
|
||||
},
|
||||
fieldMetaOptions: {
|
||||
isEnabled: true,
|
||||
sigma: 3,
|
||||
},
|
||||
type: 'ORDINAL',
|
||||
},
|
||||
type: 'DYNAMIC',
|
||||
},
|
||||
icon: {
|
||||
options: {
|
||||
value: 'marker',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
iconOrientation: {
|
||||
options: {
|
||||
orientation: 0,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
iconSize: {
|
||||
options: {
|
||||
field: {
|
||||
name: 'avg_of_transaction.duration.us',
|
||||
origin: 'source',
|
||||
},
|
||||
fieldMetaOptions: {
|
||||
isEnabled: true,
|
||||
sigma: 3,
|
||||
},
|
||||
maxSize: 32,
|
||||
minSize: 7,
|
||||
},
|
||||
type: 'DYNAMIC',
|
||||
},
|
||||
labelText: {
|
||||
options: {
|
||||
value: '',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelBorderColor: {
|
||||
options: {
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelBorderSize: {
|
||||
options: {
|
||||
size: 'SMALL',
|
||||
},
|
||||
},
|
||||
labelColor: {
|
||||
options: {
|
||||
color: '#000000',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
labelSize: {
|
||||
options: {
|
||||
size: 14,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
lineColor: {
|
||||
options: {
|
||||
color: '#3d3d3d',
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
lineWidth: {
|
||||
options: {
|
||||
size: 1,
|
||||
},
|
||||
type: 'STATIC',
|
||||
},
|
||||
symbolizeAs: {
|
||||
options: {
|
||||
value: 'circle',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
},
|
||||
type: 'VECTOR',
|
||||
visible: true,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import uuid from 'uuid/v4';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
AggDescriptor,
|
||||
ColorDynamicOptions,
|
||||
LabelDynamicOptions,
|
||||
LayerDescriptor,
|
||||
SizeDynamicOptions,
|
||||
StylePropertyField,
|
||||
VectorStylePropertiesDescriptor,
|
||||
} from '../../../../common/descriptor_types';
|
||||
import {
|
||||
AGG_TYPE,
|
||||
COLOR_MAP_TYPE,
|
||||
FIELD_ORIGIN,
|
||||
GRID_RESOLUTION,
|
||||
RENDER_AS,
|
||||
SOURCE_TYPES,
|
||||
STYLE_TYPE,
|
||||
VECTOR_STYLES,
|
||||
} from '../../../../common/constants';
|
||||
import { getJoinAggKey, getSourceAggKey } from '../../../../common/get_agg_key';
|
||||
import { OBSERVABILITY_LAYER_TYPE } from './layer_select';
|
||||
import { OBSERVABILITY_METRIC_TYPE } from './metric_select';
|
||||
import { DISPLAY } from './display_select';
|
||||
import { VectorStyle } from '../../styles/vector/vector_style';
|
||||
// @ts-ignore
|
||||
import { EMSFileSource } from '../../sources/ems_file_source';
|
||||
// @ts-ignore
|
||||
import { ESGeoGridSource } from '../../sources/es_geo_grid_source';
|
||||
import { VectorLayer } from '../../vector_layer';
|
||||
// @ts-ignore
|
||||
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';
|
||||
|
||||
const defaultDynamicProperties = getDefaultDynamicProperties();
|
||||
|
||||
function createDynamicFillColorDescriptor(
|
||||
layer: OBSERVABILITY_LAYER_TYPE,
|
||||
field: StylePropertyField
|
||||
) {
|
||||
return {
|
||||
type: STYLE_TYPE.DYNAMIC,
|
||||
options: {
|
||||
...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions),
|
||||
field,
|
||||
color:
|
||||
layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE ? 'Green to Red' : 'Yellow to Red',
|
||||
type: COLOR_MAP_TYPE.ORDINAL,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createLayerLabel(
|
||||
layer: OBSERVABILITY_LAYER_TYPE,
|
||||
metric: OBSERVABILITY_METRIC_TYPE
|
||||
): string | null {
|
||||
let layerName;
|
||||
if (layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE) {
|
||||
layerName = i18n.translate('xpack.maps.observability.apmRumPerformanceLayerName', {
|
||||
defaultMessage: 'Performance',
|
||||
});
|
||||
} else if (layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_TRAFFIC) {
|
||||
layerName = i18n.translate('xpack.maps.observability.apmRumTrafficLayerName', {
|
||||
defaultMessage: 'Traffic',
|
||||
});
|
||||
}
|
||||
|
||||
let metricName;
|
||||
if (metric === OBSERVABILITY_METRIC_TYPE.TRANSACTION_DURATION) {
|
||||
metricName = i18n.translate('xpack.maps.observability.durationMetricName', {
|
||||
defaultMessage: 'Duration',
|
||||
});
|
||||
} else if (metric === OBSERVABILITY_METRIC_TYPE.SLA_PERCENTAGE) {
|
||||
metricName = i18n.translate('xpack.maps.observability.slaPercentageMetricName', {
|
||||
defaultMessage: '% Duration of SLA',
|
||||
});
|
||||
} else if (metric === OBSERVABILITY_METRIC_TYPE.COUNT) {
|
||||
metricName = i18n.translate('xpack.maps.observability.countMetricName', {
|
||||
defaultMessage: 'Total',
|
||||
});
|
||||
} else if (metric === OBSERVABILITY_METRIC_TYPE.UNIQUE_COUNT) {
|
||||
metricName = i18n.translate('xpack.maps.observability.uniqueCountMetricName', {
|
||||
defaultMessage: 'Unique count',
|
||||
});
|
||||
}
|
||||
|
||||
return `[${layerName}] ${metricName}`;
|
||||
}
|
||||
|
||||
function createAggDescriptor(metric: OBSERVABILITY_METRIC_TYPE): AggDescriptor {
|
||||
if (metric === OBSERVABILITY_METRIC_TYPE.TRANSACTION_DURATION) {
|
||||
return {
|
||||
type: AGG_TYPE.AVG,
|
||||
field: 'transaction.duration.us',
|
||||
};
|
||||
} else if (metric === OBSERVABILITY_METRIC_TYPE.SLA_PERCENTAGE) {
|
||||
return {
|
||||
type: AGG_TYPE.AVG,
|
||||
field: 'duration_sla_pct',
|
||||
};
|
||||
} else if (metric === OBSERVABILITY_METRIC_TYPE.UNIQUE_COUNT) {
|
||||
return {
|
||||
type: AGG_TYPE.UNIQUE_COUNT,
|
||||
field: 'transaction.id',
|
||||
};
|
||||
} else {
|
||||
return { type: AGG_TYPE.COUNT };
|
||||
}
|
||||
}
|
||||
|
||||
// All APM indices match APM index pattern. Need to apply query to filter to specific document types
|
||||
// https://www.elastic.co/guide/en/kibana/current/apm-settings-kb.html
|
||||
function createAmpSourceQuery(layer: OBSERVABILITY_LAYER_TYPE) {
|
||||
// APM transaction documents
|
||||
let query;
|
||||
if (
|
||||
layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE ||
|
||||
layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_TRAFFIC
|
||||
) {
|
||||
query = 'processor.event:"transaction"';
|
||||
}
|
||||
|
||||
return query
|
||||
? {
|
||||
language: 'kuery',
|
||||
query,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function getGeoGridRequestType(display: DISPLAY): RENDER_AS {
|
||||
if (display === DISPLAY.HEATMAP) {
|
||||
return RENDER_AS.HEATMAP;
|
||||
}
|
||||
|
||||
if (display === DISPLAY.GRIDS) {
|
||||
return RENDER_AS.GRID;
|
||||
}
|
||||
|
||||
return RENDER_AS.POINT;
|
||||
}
|
||||
|
||||
export function createLayerDescriptor({
|
||||
layer,
|
||||
metric,
|
||||
display,
|
||||
}: {
|
||||
layer: OBSERVABILITY_LAYER_TYPE | null;
|
||||
metric: OBSERVABILITY_METRIC_TYPE | null;
|
||||
display: DISPLAY | null;
|
||||
}): LayerDescriptor | null {
|
||||
if (!layer || !metric || !display) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const apmSourceQuery = createAmpSourceQuery(layer);
|
||||
const label = createLayerLabel(layer, metric);
|
||||
const metricsDescriptor = createAggDescriptor(metric);
|
||||
|
||||
if (display === DISPLAY.CHOROPLETH) {
|
||||
const joinId = uuid();
|
||||
const joinKey = getJoinAggKey({
|
||||
aggType: metricsDescriptor.type,
|
||||
aggFieldName: metricsDescriptor.field ? metricsDescriptor.field : '',
|
||||
rightSourceId: joinId,
|
||||
});
|
||||
return VectorLayer.createDescriptor({
|
||||
label,
|
||||
joins: [
|
||||
{
|
||||
leftField: 'iso2',
|
||||
right: {
|
||||
type: SOURCE_TYPES.ES_TERM_SOURCE,
|
||||
id: joinId,
|
||||
indexPatternId: APM_INDEX_PATTERN_ID,
|
||||
indexPatternTitle: 'apm-*', // TODO look up from APM_OSS.indexPattern
|
||||
term: 'client.geo.country_iso_code',
|
||||
metrics: [metricsDescriptor],
|
||||
whereQuery: apmSourceQuery,
|
||||
},
|
||||
},
|
||||
],
|
||||
sourceDescriptor: EMSFileSource.createDescriptor({
|
||||
id: 'world_countries',
|
||||
tooltipProperties: ['name', 'iso2'],
|
||||
}),
|
||||
style: VectorStyle.createDescriptor({
|
||||
[VECTOR_STYLES.FILL_COLOR]: createDynamicFillColorDescriptor(layer, {
|
||||
name: joinKey,
|
||||
origin: FIELD_ORIGIN.JOIN,
|
||||
}),
|
||||
[VECTOR_STYLES.LINE_COLOR]: {
|
||||
type: STYLE_TYPE.STATIC,
|
||||
options: {
|
||||
color: '#3d3d3d',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const geoGridSourceDescriptor = ESGeoGridSource.createDescriptor({
|
||||
indexPatternId: APM_INDEX_PATTERN_ID,
|
||||
geoField: 'client.geo.location',
|
||||
metrics: [metricsDescriptor],
|
||||
requestType: getGeoGridRequestType(display),
|
||||
resolution: GRID_RESOLUTION.MOST_FINE,
|
||||
});
|
||||
|
||||
if (display === DISPLAY.HEATMAP) {
|
||||
return HeatmapLayer.createDescriptor({
|
||||
label,
|
||||
query: apmSourceQuery,
|
||||
sourceDescriptor: geoGridSourceDescriptor,
|
||||
});
|
||||
}
|
||||
|
||||
const metricSourceKey = getSourceAggKey({
|
||||
aggType: metricsDescriptor.type,
|
||||
aggFieldName: metricsDescriptor.field,
|
||||
});
|
||||
const metricStyleField = {
|
||||
name: metricSourceKey,
|
||||
origin: FIELD_ORIGIN.SOURCE,
|
||||
};
|
||||
|
||||
const styleProperties: VectorStylePropertiesDescriptor = {
|
||||
[VECTOR_STYLES.FILL_COLOR]: createDynamicFillColorDescriptor(layer, metricStyleField),
|
||||
[VECTOR_STYLES.ICON_SIZE]: {
|
||||
type: STYLE_TYPE.DYNAMIC,
|
||||
options: {
|
||||
...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions),
|
||||
field: metricStyleField,
|
||||
},
|
||||
},
|
||||
[VECTOR_STYLES.LINE_COLOR]: {
|
||||
type: STYLE_TYPE.STATIC,
|
||||
options: {
|
||||
color: '#3d3d3d',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (metric === OBSERVABILITY_METRIC_TYPE.SLA_PERCENTAGE) {
|
||||
styleProperties[VECTOR_STYLES.LABEL_TEXT] = {
|
||||
type: STYLE_TYPE.DYNAMIC,
|
||||
options: {
|
||||
...(defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT]!.options as LabelDynamicOptions),
|
||||
field: metricStyleField,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return VectorLayer.createDescriptor({
|
||||
label,
|
||||
query: apmSourceQuery,
|
||||
sourceDescriptor: geoGridSourceDescriptor,
|
||||
style: VectorStyle.createDescriptor(styleProperties),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OBSERVABILITY_LAYER_TYPE } from './layer_select';
|
||||
|
||||
export enum DISPLAY {
|
||||
CHOROPLETH = 'CHOROPLETH',
|
||||
CLUSTERS = 'CLUSTERS',
|
||||
GRIDS = 'GRIDS',
|
||||
HEATMAP = 'HEATMAP',
|
||||
}
|
||||
|
||||
const DISPLAY_OPTIONS = [
|
||||
{
|
||||
value: DISPLAY.CHOROPLETH,
|
||||
text: i18n.translate('xpack.maps.observability.choroplethLabel', {
|
||||
defaultMessage: 'World boundaries',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: DISPLAY.CLUSTERS,
|
||||
text: i18n.translate('xpack.maps.observability.clustersLabel', {
|
||||
defaultMessage: 'Clusters',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: DISPLAY.GRIDS,
|
||||
text: i18n.translate('xpack.maps.observability.gridsLabel', {
|
||||
defaultMessage: 'Grids',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: DISPLAY.HEATMAP,
|
||||
text: i18n.translate('xpack.maps.observability.heatMapLabel', {
|
||||
defaultMessage: 'Heat map',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
layer: OBSERVABILITY_LAYER_TYPE | null;
|
||||
value: DISPLAY;
|
||||
onChange: (display: DISPLAY) => void;
|
||||
}
|
||||
|
||||
export function DisplaySelect(props: Props) {
|
||||
function onChange(event: ChangeEvent<HTMLSelectElement>) {
|
||||
props.onChange(event.target.value as DISPLAY);
|
||||
}
|
||||
|
||||
if (!props.layer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.observability.displayLabel', {
|
||||
defaultMessage: 'Display',
|
||||
})}
|
||||
>
|
||||
<EuiSelect options={DISPLAY_OPTIONS} value={props.value} onChange={onChange} />
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { ObservabilityLayerWizardConfig } from './observability_layer_wizard';
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export enum OBSERVABILITY_LAYER_TYPE {
|
||||
APM_RUM_PERFORMANCE = 'APM_RUM_PERFORMANCE',
|
||||
APM_RUM_TRAFFIC = 'APM_RUM_TRAFFIC',
|
||||
}
|
||||
|
||||
const OBSERVABILITY_LAYER_OPTIONS = [
|
||||
{
|
||||
value: OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE,
|
||||
text: i18n.translate('xpack.maps.observability.apmRumPerformanceLabel', {
|
||||
defaultMessage: 'APM RUM Performance',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: OBSERVABILITY_LAYER_TYPE.APM_RUM_TRAFFIC,
|
||||
text: i18n.translate('xpack.maps.observability.apmRumTrafficLabel', {
|
||||
defaultMessage: 'APM RUM Traffic',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
value: OBSERVABILITY_LAYER_TYPE | null;
|
||||
onChange: (layer: OBSERVABILITY_LAYER_TYPE) => void;
|
||||
}
|
||||
|
||||
export function LayerSelect(props: Props) {
|
||||
function onChange(event: ChangeEvent<HTMLSelectElement>) {
|
||||
props.onChange(event.target.value as OBSERVABILITY_LAYER_TYPE);
|
||||
}
|
||||
|
||||
const options = props.value
|
||||
? OBSERVABILITY_LAYER_OPTIONS
|
||||
: [{ value: '', text: '' }, ...OBSERVABILITY_LAYER_OPTIONS];
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.observability.layerLabel', {
|
||||
defaultMessage: 'Layer',
|
||||
})}
|
||||
>
|
||||
<EuiSelect options={options} value={props.value ? props.value : ''} onChange={onChange} />
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OBSERVABILITY_LAYER_TYPE } from './layer_select';
|
||||
|
||||
export enum OBSERVABILITY_METRIC_TYPE {
|
||||
TRANSACTION_DURATION = 'TRANSACTION_DURATION',
|
||||
SLA_PERCENTAGE = 'SLA_PERCENTAGE',
|
||||
COUNT = 'COUNT',
|
||||
UNIQUE_COUNT = 'UNIQUE_COUNT',
|
||||
}
|
||||
|
||||
const APM_RUM_PERFORMANCE_METRIC_OPTIONS = [
|
||||
{
|
||||
value: OBSERVABILITY_METRIC_TYPE.TRANSACTION_DURATION,
|
||||
text: i18n.translate('xpack.maps.observability.transactionDurationLabel', {
|
||||
defaultMessage: 'Transaction duraction',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: OBSERVABILITY_METRIC_TYPE.SLA_PERCENTAGE,
|
||||
text: i18n.translate('xpack.maps.observability.slaPercentageLabel', {
|
||||
defaultMessage: 'SLA percentage',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const APM_RUM_TRAFFIC_METRIC_OPTIONS = [
|
||||
{
|
||||
value: OBSERVABILITY_METRIC_TYPE.COUNT,
|
||||
text: i18n.translate('xpack.maps.observability.countLabel', {
|
||||
defaultMessage: 'Count',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: OBSERVABILITY_METRIC_TYPE.UNIQUE_COUNT,
|
||||
text: i18n.translate('xpack.maps.observability.uniqueCountLabel', {
|
||||
defaultMessage: 'Unique count',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export function getMetricOptionsForLayer(layer: OBSERVABILITY_LAYER_TYPE): EuiSelectOption[] {
|
||||
if (layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE) {
|
||||
return APM_RUM_PERFORMANCE_METRIC_OPTIONS;
|
||||
}
|
||||
|
||||
if (layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_TRAFFIC) {
|
||||
return APM_RUM_TRAFFIC_METRIC_OPTIONS;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
layer: OBSERVABILITY_LAYER_TYPE | null;
|
||||
value: OBSERVABILITY_METRIC_TYPE | null;
|
||||
onChange: (metricType: OBSERVABILITY_METRIC_TYPE) => void;
|
||||
}
|
||||
|
||||
export function MetricSelect(props: Props) {
|
||||
function onChange(event: ChangeEvent<HTMLSelectElement>) {
|
||||
props.onChange(event.target.value as OBSERVABILITY_METRIC_TYPE);
|
||||
}
|
||||
|
||||
if (!props.layer || !props.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.observability.metricLabel', {
|
||||
defaultMessage: 'Metric',
|
||||
})}
|
||||
>
|
||||
<EuiSelect
|
||||
options={getMetricOptionsForLayer(props.layer)}
|
||||
value={props.value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { RenderWizardArguments } from '../../layer_wizard_registry';
|
||||
import { LayerSelect, OBSERVABILITY_LAYER_TYPE } from './layer_select';
|
||||
import { getMetricOptionsForLayer, MetricSelect, OBSERVABILITY_METRIC_TYPE } from './metric_select';
|
||||
import { DisplaySelect, DISPLAY } from './display_select';
|
||||
import { createLayerDescriptor } from './create_layer_descriptor';
|
||||
|
||||
interface State {
|
||||
display: DISPLAY;
|
||||
layer: OBSERVABILITY_LAYER_TYPE | null;
|
||||
metric: OBSERVABILITY_METRIC_TYPE | null;
|
||||
}
|
||||
|
||||
export class ObservabilityLayerTemplate extends Component<RenderWizardArguments, State> {
|
||||
state = {
|
||||
layer: null,
|
||||
metric: null,
|
||||
display: DISPLAY.CHOROPLETH,
|
||||
};
|
||||
|
||||
_onLayerChange = (layer: OBSERVABILITY_LAYER_TYPE) => {
|
||||
const newState = { layer, metric: this.state.metric };
|
||||
|
||||
// Select metric when layer change invalidates selected metric.
|
||||
const metricOptions = getMetricOptionsForLayer(layer);
|
||||
const selectedMetricOption = metricOptions.find(option => {
|
||||
return option.value === this.state.metric;
|
||||
});
|
||||
if (!selectedMetricOption) {
|
||||
if (metricOptions.length) {
|
||||
// @ts-ignore
|
||||
newState.metric = metricOptions[0].value;
|
||||
} else {
|
||||
newState.metric = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState(newState, this._previewLayer);
|
||||
};
|
||||
|
||||
_onMetricChange = (metric: OBSERVABILITY_METRIC_TYPE) => {
|
||||
this.setState({ metric }, this._previewLayer);
|
||||
};
|
||||
|
||||
_onDisplayChange = (display: DISPLAY) => {
|
||||
this.setState({ display }, this._previewLayer);
|
||||
};
|
||||
|
||||
_previewLayer() {
|
||||
this.props.previewLayer(
|
||||
createLayerDescriptor({
|
||||
layer: this.state.layer,
|
||||
metric: this.state.metric,
|
||||
display: this.state.display,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<LayerSelect value={this.state.layer} onChange={this._onLayerChange} />
|
||||
<MetricSelect
|
||||
layer={this.state.layer}
|
||||
value={this.state.metric}
|
||||
onChange={this._onMetricChange}
|
||||
/>
|
||||
<DisplaySelect
|
||||
layer={this.state.layer}
|
||||
value={this.state.display}
|
||||
onChange={this._onDisplayChange}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
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';
|
||||
|
||||
export const ObservabilityLayerWizardConfig: LayerWizard = {
|
||||
checkVisibility: async () => {
|
||||
try {
|
||||
await getIndexPatternService().get(APM_INDEX_PATTERN_ID);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
description: i18n.translate('xpack.maps.observability.desc', {
|
||||
defaultMessage: 'APM layers',
|
||||
}),
|
||||
icon: 'logoObservability',
|
||||
renderWizard: (renderWizardArguments: RenderWizardArguments) => {
|
||||
return <ObservabilityLayerTemplate {...renderWizardArguments} />;
|
||||
},
|
||||
title: i18n.translate('xpack.maps.observability.title', {
|
||||
defaultMessage: 'Observability',
|
||||
}),
|
||||
};
|
|
@ -7,13 +7,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { AbstractESSource } from '../es_source';
|
||||
import { esAggFieldsFactory } from '../../fields/es_agg_field';
|
||||
import {
|
||||
AGG_DELIMITER,
|
||||
AGG_TYPE,
|
||||
COUNT_PROP_LABEL,
|
||||
COUNT_PROP_NAME,
|
||||
FIELD_ORIGIN,
|
||||
} from '../../../../common/constants';
|
||||
import { AGG_TYPE, COUNT_PROP_LABEL, FIELD_ORIGIN } from '../../../../common/constants';
|
||||
import { getSourceAggKey } from '../../../../common/get_agg_key';
|
||||
|
||||
export class AbstractESAggSource extends AbstractESSource {
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
|
@ -59,7 +54,10 @@ export class AbstractESAggSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
getAggKey(aggType, fieldName) {
|
||||
return aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME;
|
||||
return getSourceAggKey({
|
||||
aggType,
|
||||
aggFieldName: fieldName,
|
||||
});
|
||||
}
|
||||
|
||||
getAggLabel(aggType, fieldName) {
|
||||
|
|
|
@ -35,13 +35,14 @@ export const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle
|
|||
export class ESGeoGridSource extends AbstractESAggSource {
|
||||
static type = SOURCE_TYPES.ES_GEO_GRID;
|
||||
|
||||
static createDescriptor({ indexPatternId, geoField, requestType, resolution }) {
|
||||
static createDescriptor({ indexPatternId, geoField, metrics, requestType, resolution }) {
|
||||
return {
|
||||
type: ESGeoGridSource.type,
|
||||
id: uuid(),
|
||||
indexPatternId: indexPatternId,
|
||||
geoField: geoField,
|
||||
requestType: requestType,
|
||||
indexPatternId,
|
||||
geoField,
|
||||
metrics: metrics ? metrics : [],
|
||||
requestType,
|
||||
resolution: resolution ? resolution : GRID_RESOLUTION.COARSE,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,9 +18,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
// @ts-ignore
|
||||
import { SingleFieldSelect } from '../../../components/single_field_select';
|
||||
|
||||
// @ts-ignore
|
||||
import { indexPatternService } from '../../../kibana_services';
|
||||
import { getIndexPatternService } from '../../../kibana_services';
|
||||
// @ts-ignore
|
||||
import { ValidatedRange } from '../../../components/validated_range';
|
||||
import {
|
||||
|
@ -68,8 +66,10 @@ export class ScalingForm extends Component<Props, State> {
|
|||
|
||||
async loadIndexSettings() {
|
||||
try {
|
||||
const indexPattern = await indexPatternService.get(this.props.indexPatternId);
|
||||
const { maxInnerResultWindow, maxResultWindow } = await loadIndexSettings(indexPattern.title);
|
||||
const indexPattern = await getIndexPatternService().get(this.props.indexPatternId);
|
||||
const { maxInnerResultWindow, maxResultWindow } = await loadIndexSettings(
|
||||
indexPattern!.title
|
||||
);
|
||||
if (this._isMounted) {
|
||||
this.setState({ maxInnerResultWindow, maxResultWindow });
|
||||
}
|
||||
|
|
|
@ -7,8 +7,13 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AGG_TYPE, DEFAULT_MAX_BUCKETS_LIMIT, FIELD_ORIGIN } from '../../../../common/constants';
|
||||
import { getJoinAggKey } from '../../../../common/get_join_key';
|
||||
import {
|
||||
AGG_TYPE,
|
||||
DEFAULT_MAX_BUCKETS_LIMIT,
|
||||
FIELD_ORIGIN,
|
||||
SOURCE_TYPES,
|
||||
} from '../../../../common/constants';
|
||||
import { getJoinAggKey } from '../../../../common/get_agg_key';
|
||||
import { ESDocField } from '../../fields/es_doc_field';
|
||||
import { AbstractESAggSource } from '../es_agg_source';
|
||||
import { getField, addFieldToDSL, extractPropertiesFromBucket } from '../../util/es_agg_utils';
|
||||
|
@ -30,7 +35,7 @@ export function extractPropertiesMap(rawEsData, countPropertyName) {
|
|||
}
|
||||
|
||||
export class ESTermSource extends AbstractESAggSource {
|
||||
static type = 'ES_TERM_SOURCE';
|
||||
static type = SOURCE_TYPES.ES_TERM_SOURCE;
|
||||
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(descriptor, inspectorAdapters);
|
||||
|
|
|
@ -16,7 +16,7 @@ import { TileLayer } from '../../tile_layer';
|
|||
import { getKibanaTileMap } from '../../../meta';
|
||||
|
||||
export const kibanaBasemapLayerWizardConfig: LayerWizard = {
|
||||
checkVisibility: () => {
|
||||
checkVisibility: async () => {
|
||||
const tilemap = getKibanaTileMap();
|
||||
return !!tilemap.url;
|
||||
},
|
||||
|
|
|
@ -9,7 +9,6 @@ import { AbstractLayer } from './layer';
|
|||
import { IVectorSource } from './sources/vector_source';
|
||||
import {
|
||||
MapFilters,
|
||||
LayerDescriptor,
|
||||
VectorLayerDescriptor,
|
||||
VectorSourceRequestMeta,
|
||||
} from '../../common/descriptor_types';
|
||||
|
@ -35,7 +34,7 @@ export interface IVectorLayer extends ILayer {
|
|||
export class VectorLayer extends AbstractLayer implements IVectorLayer {
|
||||
protected readonly _style: IVectorStyle;
|
||||
static createDescriptor(
|
||||
options: Partial<LayerDescriptor>,
|
||||
options: Partial<VectorLayerDescriptor>,
|
||||
mapColors?: string[]
|
||||
): VectorLayerDescriptor;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue