mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
This introduces the `AbstractField` class and its implementations. Their use replace the ad-hoc object literals that were used to pass around field-level metadata in joins, metrics, styles, and tooltips.
This commit is contained in:
parent
4c14ca66ae
commit
820d61d977
56 changed files with 1124 additions and 723 deletions
|
@ -3,6 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const EMS_CATALOGUE_PATH = 'ems/catalogue';
|
||||
|
||||
|
@ -114,3 +115,15 @@ export const METRIC_TYPE = {
|
|||
SUM: 'sum',
|
||||
UNIQUE_COUNT: 'cardinality',
|
||||
};
|
||||
|
||||
export const COUNT_AGG_TYPE = METRIC_TYPE.COUNT;
|
||||
export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLabel', {
|
||||
defaultMessage: 'count'
|
||||
});
|
||||
|
||||
export const COUNT_PROP_NAME = 'doc_count';
|
||||
|
||||
export const STYLE_TYPE = {
|
||||
'STATIC': 'STATIC',
|
||||
'DYNAMIC': 'DYNAMIC'
|
||||
};
|
||||
|
|
|
@ -732,9 +732,7 @@ export function clearMissingStyleProperties(layerId) {
|
|||
return;
|
||||
}
|
||||
|
||||
const dateFields = await targetLayer.getDateFields();
|
||||
const numberFields = await targetLayer.getNumberFields();
|
||||
const ordinalFields = [...dateFields, ...numberFields];
|
||||
const ordinalFields = await targetLayer.getOrdinalFields();
|
||||
const { hasChanges, nextStyleDescriptor } = style.getDescriptorWithMissingStylePropsRemoved(ordinalFields);
|
||||
if (hasChanges) {
|
||||
dispatch(updateLayerStyle(layerId, nextStyleDescriptor));
|
||||
|
|
|
@ -41,6 +41,7 @@ exports[`TooltipSelector should render component 1`] = `
|
|||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"label": "foobar_label",
|
||||
"name": "iso3",
|
||||
"type": "string",
|
||||
},
|
||||
|
@ -50,7 +51,9 @@ exports[`TooltipSelector should render component 1`] = `
|
|||
selectedFields={
|
||||
Array [
|
||||
Object {
|
||||
"label": "foobar_label",
|
||||
"name": "iso2",
|
||||
"type": "foobar_type",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -30,35 +30,109 @@ const reorder = (list, startIndex, endIndex) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const getProps = async field => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const label = await field.getLabel();
|
||||
const type = await field.getDataType();
|
||||
resolve({
|
||||
label: label,
|
||||
type: type,
|
||||
name: field.getName()
|
||||
});
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export class TooltipSelector extends Component {
|
||||
|
||||
_getPropertyLabel = (propertyName) => {
|
||||
if (!this.props.fields) {
|
||||
return propertyName;
|
||||
state = {
|
||||
fieldProps: [],
|
||||
selectedFieldProps: []
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._isMounted = false;
|
||||
this._previousFields = null;
|
||||
this._previousSelectedTooltips = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadFieldProps();
|
||||
this._loadTooltipFieldProps();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._loadTooltipFieldProps();
|
||||
this._loadFieldProps();
|
||||
}
|
||||
|
||||
async _loadTooltipFieldProps() {
|
||||
|
||||
if (!this.props.tooltipFields || this.props.tooltipFields === this._previousSelectedTooltips) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = this.props.fields.find(field => {
|
||||
this._previousSelectedTooltips = this.props.tooltipFields;
|
||||
const selectedProps = this.props.tooltipFields.map(getProps);
|
||||
const selectedFieldProps = await Promise.all(selectedProps);
|
||||
if (this._isMounted) {
|
||||
this.setState({ selectedFieldProps });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async _loadFieldProps() {
|
||||
|
||||
if (!this.props.fields || this.props.fields === this._previousFields) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._previousFields = this.props.fields;
|
||||
const props = this.props.fields.map(getProps);
|
||||
const fieldProps = await Promise.all(props);
|
||||
if (this._isMounted) {
|
||||
this.setState({ fieldProps });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_getPropertyLabel = (propertyName) => {
|
||||
if (!this.state.fieldProps.length) {
|
||||
return propertyName;
|
||||
}
|
||||
const prop = this.state.fieldProps.find((field) => {
|
||||
return field.name === propertyName;
|
||||
});
|
||||
return prop.label ? prop.label : propertyName;
|
||||
}
|
||||
|
||||
return field && field.label
|
||||
? field.label
|
||||
: propertyName;
|
||||
_getTooltipProperties() {
|
||||
return this.props.tooltipFields.map(field => field.getName());
|
||||
}
|
||||
|
||||
_onAdd = (properties) => {
|
||||
if (!this.props.tooltipProperties) {
|
||||
if (!this.props.tooltipFields) {
|
||||
this.props.onChange([...properties]);
|
||||
} else {
|
||||
this.props.onChange([...this.props.tooltipProperties, ...properties]);
|
||||
const existingProperties = this._getTooltipProperties();
|
||||
this.props.onChange([...existingProperties, ...properties]);
|
||||
}
|
||||
}
|
||||
|
||||
_removeProperty = (index) => {
|
||||
if (!this.props.tooltipProperties) {
|
||||
if (!this.props.tooltipFields) {
|
||||
this.props.onChange([]);
|
||||
} else {
|
||||
const tooltipProperties = [...this.props.tooltipProperties];
|
||||
const tooltipProperties = this._getTooltipProperties();
|
||||
tooltipProperties.splice(index, 1);
|
||||
this.props.onChange(tooltipProperties);
|
||||
}
|
||||
|
@ -70,11 +144,11 @@ export class TooltipSelector extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
this.props.onChange(reorder(this.props.tooltipProperties, source.index, destination.index));
|
||||
this.props.onChange(reorder(this._getTooltipProperties(), source.index, destination.index));
|
||||
};
|
||||
|
||||
_renderProperties() {
|
||||
if (!this.props.tooltipProperties) {
|
||||
if (!this.state.selectedFieldProps.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -82,12 +156,12 @@ export class TooltipSelector extends Component {
|
|||
<EuiDragDropContext onDragEnd={this._onDragEnd}>
|
||||
<EuiDroppable droppableId="mapLayerTOC" spacing="none">
|
||||
{(provided, snapshot) => (
|
||||
this.props.tooltipProperties.map((propertyName, idx) => (
|
||||
this.state.selectedFieldProps.map((field, idx) => (
|
||||
<EuiDraggable
|
||||
spacing="none"
|
||||
key={propertyName}
|
||||
key={field.name}
|
||||
index={idx}
|
||||
draggableId={propertyName}
|
||||
draggableId={field.name}
|
||||
customDragHandle={true}
|
||||
disableInteractiveElementBlocking // Allows button to be drag handle
|
||||
>
|
||||
|
@ -99,7 +173,7 @@ export class TooltipSelector extends Component {
|
|||
})}
|
||||
>
|
||||
<EuiText className="mapTooltipSelector__propertyContent" size="s">
|
||||
{this._getPropertyLabel(propertyName)}
|
||||
{this._getPropertyLabel(field.name)}
|
||||
</EuiText>
|
||||
<div className="mapTooltipSelector__propertyIcons">
|
||||
<EuiButtonIcon
|
||||
|
@ -137,13 +211,6 @@ export class TooltipSelector extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
|
||||
const selectedFields = this.props.tooltipProperties
|
||||
? this.props.tooltipProperties.map(propertyName => {
|
||||
return { name: propertyName };
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiTitle size="xxs">
|
||||
|
@ -160,11 +227,12 @@ export class TooltipSelector extends Component {
|
|||
<EuiTextAlign textAlign="center">
|
||||
<AddTooltipFieldPopover
|
||||
onAdd={this._onAdd}
|
||||
fields={this.props.fields}
|
||||
selectedFields={selectedFields}
|
||||
fields={this.state.fieldProps}
|
||||
selectedFields={this.state.selectedFieldProps}
|
||||
/>
|
||||
</EuiTextAlign>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,33 +9,59 @@ import { shallow } from 'enzyme';
|
|||
|
||||
import { TooltipSelector } from './tooltip_selector';
|
||||
|
||||
|
||||
class MockField {
|
||||
constructor({ name, label, type }) {
|
||||
this._name = name;
|
||||
this._label = label;
|
||||
this._type = type;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
async getLabel() {
|
||||
return this._label || 'foobar_label';
|
||||
}
|
||||
|
||||
async getDataType() {
|
||||
return this._type || 'foobar_type';
|
||||
}
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
tooltipProperties: ['iso2'],
|
||||
tooltipFields: [new MockField({ name: 'iso2' })],
|
||||
onChange: (()=>{}),
|
||||
fields: [
|
||||
{
|
||||
new MockField({
|
||||
name: 'iso2',
|
||||
label: 'ISO 3166-1 alpha-2 code',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
}),
|
||||
new MockField({
|
||||
name: 'iso3',
|
||||
type: 'string'
|
||||
},
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
describe('TooltipSelector', () => {
|
||||
|
||||
test('should render component', async () => {
|
||||
|
||||
const component = shallow(
|
||||
<TooltipSelector
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
expect(component).toMatchSnapshot();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -111,7 +111,14 @@ export class Join extends Component {
|
|||
async _loadLeftFields() {
|
||||
let leftFields;
|
||||
try {
|
||||
leftFields = await this.props.layer.getLeftJoinFields();
|
||||
const leftFieldsInstances = await this.props.layer.getLeftJoinFields();
|
||||
const leftFieldPromises = leftFieldsInstances.map(async (field) => {
|
||||
return {
|
||||
name: field.getName(),
|
||||
label: await field.getLabel()
|
||||
};
|
||||
});
|
||||
leftFields = await Promise.all(leftFieldPromises);
|
||||
} catch (error) {
|
||||
leftFields = [];
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@ export class FeatureProperties extends React.Component {
|
|||
}
|
||||
|
||||
const rows = this.state.properties.map(tooltipProperty => {
|
||||
|
||||
const label = tooltipProperty.getPropertyName();
|
||||
return (
|
||||
<tr key={label}>
|
||||
|
|
|
@ -19,10 +19,10 @@ exports[`TOCEntry is rendered 1`] = `
|
|||
Object {
|
||||
"getDisplayName": [Function],
|
||||
"getId": [Function],
|
||||
"getLegendDetails": [Function],
|
||||
"hasErrors": [Function],
|
||||
"hasLegendDetails": [Function],
|
||||
"isVisible": [Function],
|
||||
"renderLegendDetails": [Function],
|
||||
"showAtZoomLevel": [Function],
|
||||
}
|
||||
}
|
||||
|
@ -87,10 +87,10 @@ exports[`TOCEntry props isReadOnly 1`] = `
|
|||
Object {
|
||||
"getDisplayName": [Function],
|
||||
"getId": [Function],
|
||||
"getLegendDetails": [Function],
|
||||
"hasErrors": [Function],
|
||||
"hasLegendDetails": [Function],
|
||||
"isVisible": [Function],
|
||||
"renderLegendDetails": [Function],
|
||||
"showAtZoomLevel": [Function],
|
||||
}
|
||||
}
|
||||
|
@ -137,10 +137,10 @@ exports[`TOCEntry props should display layer details when isLegendDetailsOpen is
|
|||
Object {
|
||||
"getDisplayName": [Function],
|
||||
"getId": [Function],
|
||||
"getLegendDetails": [Function],
|
||||
"hasErrors": [Function],
|
||||
"hasLegendDetails": [Function],
|
||||
"isVisible": [Function],
|
||||
"renderLegendDetails": [Function],
|
||||
"showAtZoomLevel": [Function],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ export class TOCEntry extends React.Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
const tocDetails = this.props.layer.getLegendDetails();
|
||||
const tocDetails = this.props.layer.renderLegendDetails();
|
||||
if (!tocDetails) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ const LAYER_ID = '1';
|
|||
|
||||
const mockLayer = {
|
||||
getId: () => { return LAYER_ID; },
|
||||
getLegendDetails: () => { return (<div>TOC details mock</div>); },
|
||||
renderLegendDetails: () => { return (<div>TOC details mock</div>); },
|
||||
getDisplayName: () => { return 'layer 1'; },
|
||||
isVisible: () => { return true; },
|
||||
showAtZoomLevel: () => { return true; },
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { AbstractField } from './field';
|
||||
import { TooltipProperty } from '../tooltips/tooltip_property';
|
||||
|
||||
export class EMSFileField extends AbstractField {
|
||||
static type = 'EMS_FILE';
|
||||
|
||||
async getLabel() {
|
||||
const emsFileLayer = await this._source.getEMSFileLayer();
|
||||
const emsFields = emsFileLayer.getFieldsInLanguage();
|
||||
// Map EMS field name to language specific label
|
||||
const emsField = emsFields.find(field => field.name === this.getName());
|
||||
return emsField ? emsField.description : this.getName();
|
||||
}
|
||||
|
||||
async createTooltipProperty(value) {
|
||||
const label = await this.getLabel();
|
||||
return new TooltipProperty(this.getName(), label, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { AbstractField } from './field';
|
||||
import { COUNT_AGG_TYPE } from '../../../common/constants';
|
||||
import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property';
|
||||
|
||||
export class ESAggMetricField extends AbstractField {
|
||||
|
||||
static type = 'ES_AGG';
|
||||
|
||||
constructor({ label, source, aggType, esDocField, origin }) {
|
||||
super({ source, origin });
|
||||
this._label = label;
|
||||
this._aggType = aggType;
|
||||
this._esDocField = esDocField;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this._source.formatMetricKey(this.getAggType(), this.getESDocFieldName());
|
||||
}
|
||||
|
||||
async getLabel() {
|
||||
return this._label ? await this._label : this._source.formatMetricLabel(this.getAggType(), this.getESDocFieldName());
|
||||
}
|
||||
|
||||
getAggType() {
|
||||
return this._aggType;
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return (this.getAggType() === COUNT_AGG_TYPE) ? true : !!this._esDocField;
|
||||
}
|
||||
|
||||
getESDocFieldName() {
|
||||
return this._esDocField ? this._esDocField.getName() : '';
|
||||
}
|
||||
|
||||
getRequestDescription() {
|
||||
return this.getAggType() !== COUNT_AGG_TYPE ? `${this.getAggType()} ${this.getESDocFieldName()}` : COUNT_AGG_TYPE;
|
||||
}
|
||||
|
||||
async createTooltipProperty(value) {
|
||||
const indexPattern = await this._source.getIndexPattern();
|
||||
return new ESAggMetricTooltipProperty(
|
||||
this.getName(),
|
||||
await this.getLabel(),
|
||||
value,
|
||||
indexPattern,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
makeMetricAggConfig() {
|
||||
const metricAggConfig = {
|
||||
id: this.getName(),
|
||||
enabled: true,
|
||||
type: this.getAggType(),
|
||||
schema: 'metric',
|
||||
params: {}
|
||||
};
|
||||
if (this.getAggType() !== COUNT_AGG_TYPE) {
|
||||
metricAggConfig.params = { field: this.getESDocFieldName() };
|
||||
}
|
||||
return metricAggConfig;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { AbstractField } from './field';
|
||||
import { ESTooltipProperty } from '../tooltips/es_tooltip_property';
|
||||
|
||||
export class ESDocField extends AbstractField {
|
||||
|
||||
static type = 'ES_DOC';
|
||||
|
||||
async _getField() {
|
||||
const indexPattern = await this._source.getIndexPattern();
|
||||
return indexPattern.fields.getByName(this._fieldName);
|
||||
}
|
||||
|
||||
async createTooltipProperty(value) {
|
||||
const indexPattern = await this._source.getIndexPattern();
|
||||
return new ESTooltipProperty(this.getName(), this.getName(), value, indexPattern);
|
||||
}
|
||||
|
||||
async getDataType() {
|
||||
const field = await this._getField();
|
||||
return field.type;
|
||||
}
|
||||
|
||||
}
|
45
x-pack/legacy/plugins/maps/public/layers/fields/field.js
Normal file
45
x-pack/legacy/plugins/maps/public/layers/fields/field.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { FIELD_ORIGIN } from '../../../common/constants';
|
||||
|
||||
export class AbstractField {
|
||||
|
||||
constructor({ fieldName, source, origin }) {
|
||||
this._fieldName = fieldName;
|
||||
this._source = source;
|
||||
this._origin = origin || FIELD_ORIGIN.SOURCE;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this._fieldName;
|
||||
}
|
||||
|
||||
getSource() {
|
||||
return this._source;
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this._fieldName;
|
||||
}
|
||||
|
||||
async getDataType() {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
async getLabel() {
|
||||
return this._fieldName;
|
||||
}
|
||||
|
||||
async createTooltipProperty() {
|
||||
throw new Error('must implement Field#createTooltipProperty');
|
||||
}
|
||||
|
||||
getOrigin() {
|
||||
return this._origin;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { AbstractField } from './field';
|
||||
import { TooltipProperty } from '../tooltips/tooltip_property';
|
||||
|
||||
export class KibanaRegionField extends AbstractField {
|
||||
|
||||
static type = 'KIBANA_REGION';
|
||||
|
||||
async getLabel() {
|
||||
const meta = await this._source.getVectorFileMeta();
|
||||
const field = meta.fields.find(f => f.name === this._fieldName);
|
||||
return field ? field.description : this._fieldName;
|
||||
}
|
||||
|
||||
async createTooltipProperty(value) {
|
||||
const label = await this.getLabel();
|
||||
return new TooltipProperty(this.getName(), label, value);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { AbstractLayer } from './layer';
|
||||
import { VectorLayer } from './vector_layer';
|
||||
import { HeatmapStyle } from './styles/heatmap/heatmap_style';
|
||||
|
@ -23,17 +22,19 @@ export class HeatmapLayer extends VectorLayer {
|
|||
return heatmapLayerDescriptor;
|
||||
}
|
||||
|
||||
constructor({ layerDescriptor, source, style }) {
|
||||
super({ layerDescriptor, source, style });
|
||||
if (!style) {
|
||||
constructor({ layerDescriptor, source }) {
|
||||
super({ layerDescriptor, source });
|
||||
if (!layerDescriptor.style) {
|
||||
const defaultStyle = HeatmapStyle.createDescriptor();
|
||||
this._style = new HeatmapStyle(defaultStyle);
|
||||
} else {
|
||||
this._style = new HeatmapStyle(layerDescriptor.style);
|
||||
}
|
||||
}
|
||||
|
||||
_getPropKeyOfSelectedMetric() {
|
||||
const metricfields = this._source.getMetricFields();
|
||||
return metricfields[0].propertyKey;
|
||||
return metricfields[0].getName();
|
||||
}
|
||||
|
||||
_getHeatmapLayerId() {
|
||||
|
@ -101,8 +102,8 @@ export class HeatmapLayer extends VectorLayer {
|
|||
return true;
|
||||
}
|
||||
|
||||
getLegendDetails() {
|
||||
const label = _.get(this._source.getMetricFields(), '[0].propertyLabel', '');
|
||||
return this._style.getLegendDetails(label);
|
||||
renderLegendDetails() {
|
||||
const metricFields = this._source.getMetricFields();
|
||||
return this._style.renderLegendDetails(metricFields[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,15 @@
|
|||
|
||||
|
||||
import { ESTermSource } from '../sources/es_term_source';
|
||||
import { VectorStyle } from '../styles/vector/vector_style';
|
||||
import { getComputedFieldNamePrefix } from '../styles/vector/style_util';
|
||||
|
||||
export class InnerJoin {
|
||||
|
||||
constructor(joinDescriptor, inspectorAdapters) {
|
||||
constructor(joinDescriptor, leftSource) {
|
||||
this._descriptor = joinDescriptor;
|
||||
const inspectorAdapters = leftSource.getInspectorAdapters();
|
||||
this._rightSource = new ESTermSource(joinDescriptor.right, inspectorAdapters);
|
||||
this._leftField = this._descriptor.leftField ? leftSource.createField({ fieldName: joinDescriptor.leftField }) : null;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -20,21 +22,15 @@ export class InnerJoin {
|
|||
}
|
||||
|
||||
hasCompleteConfig() {
|
||||
if (this._descriptor.leftField && this._rightSource) {
|
||||
if (this._leftField && this._rightSource) {
|
||||
return this._rightSource.hasCompleteConfig();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getRightMetricFields() {
|
||||
return this._rightSource.getMetricFields();
|
||||
}
|
||||
|
||||
getJoinFields() {
|
||||
return this.getRightMetricFields().map(({ propertyKey: name, propertyLabel: label }) => {
|
||||
return { label, name };
|
||||
});
|
||||
return this._rightSource.getMetricFields();
|
||||
}
|
||||
|
||||
// Source request id must be static and unique because the re-fetch logic uses the id to locate the previous request.
|
||||
|
@ -44,18 +40,19 @@ export class InnerJoin {
|
|||
return `join_source_${this._rightSource.getId()}`;
|
||||
}
|
||||
|
||||
getLeftFieldName() {
|
||||
return this._descriptor.leftField;
|
||||
getLeftField() {
|
||||
return this._leftField;
|
||||
}
|
||||
|
||||
joinPropertiesToFeature(feature, propertiesMap, rightMetricFields) {
|
||||
joinPropertiesToFeature(feature, propertiesMap) {
|
||||
const rightMetricFields = this._rightSource.getMetricFields();
|
||||
// delete feature properties added by previous join
|
||||
for (let j = 0; j < rightMetricFields.length; j++) {
|
||||
const { propertyKey: metricPropertyKey } = rightMetricFields[j];
|
||||
const metricPropertyKey = rightMetricFields[j].getName();
|
||||
delete feature.properties[metricPropertyKey];
|
||||
|
||||
// delete all dynamic properties for metric field
|
||||
const stylePropertyPrefix = VectorStyle.getComputedFieldNamePrefix(metricPropertyKey);
|
||||
const stylePropertyPrefix = getComputedFieldNamePrefix(metricPropertyKey);
|
||||
Object.keys(feature.properties).forEach(featurePropertyKey => {
|
||||
if (featurePropertyKey.length >= stylePropertyPrefix.length &&
|
||||
featurePropertyKey.substring(0, stylePropertyPrefix.length) === stylePropertyPrefix) {
|
||||
|
@ -64,7 +61,7 @@ export class InnerJoin {
|
|||
});
|
||||
}
|
||||
|
||||
const joinKey = feature.properties[this._descriptor.leftField];
|
||||
const joinKey = feature.properties[this._leftField.getName()];
|
||||
const coercedKey = typeof joinKey === 'undefined' || joinKey === null ? null : joinKey.toString();
|
||||
if (propertiesMap && coercedKey !== null && propertiesMap.has(coercedKey)) {
|
||||
Object.assign(feature.properties, propertiesMap.get(coercedKey));
|
||||
|
|
|
@ -23,12 +23,25 @@ const rightSource = {
|
|||
indexPatternId: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
indexPatternTitle: 'kibana_sample_data_logs',
|
||||
term: 'geo.dest',
|
||||
metrics: [{ type: 'count' }]
|
||||
};
|
||||
|
||||
const mockSource = {
|
||||
getInspectorAdapters() {
|
||||
},
|
||||
createField({ fieldName: name }) {
|
||||
return {
|
||||
getName() {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const leftJoin = new InnerJoin({
|
||||
leftField: 'iso2',
|
||||
right: rightSource
|
||||
});
|
||||
}, mockSource);
|
||||
const COUNT_PROPERTY_NAME = '__kbnjoin__count_groupby_kibana_sample_data_logs.geo.dest';
|
||||
|
||||
describe('joinPropertiesToFeature', () => {
|
||||
|
@ -76,7 +89,7 @@ describe('joinPropertiesToFeature', () => {
|
|||
const leftJoin = new InnerJoin({
|
||||
leftField: 'zipcode',
|
||||
right: rightSource
|
||||
});
|
||||
}, mockSource);
|
||||
|
||||
const feature = {
|
||||
properties: {
|
||||
|
@ -118,7 +131,7 @@ describe('joinPropertiesToFeature', () => {
|
|||
const leftJoin = new InnerJoin({
|
||||
leftField: 'code',
|
||||
right: rightSource
|
||||
});
|
||||
}, mockSource);
|
||||
|
||||
const feature = {
|
||||
properties: {
|
||||
|
|
|
@ -24,10 +24,9 @@ const NO_SOURCE_UPDATE_REQUIRED = false;
|
|||
|
||||
export class AbstractLayer {
|
||||
|
||||
constructor({ layerDescriptor, source, style }) {
|
||||
constructor({ layerDescriptor, source }) {
|
||||
this._descriptor = AbstractLayer.createDescriptor(layerDescriptor);
|
||||
this._source = source;
|
||||
this._style = style;
|
||||
if (this._descriptor.__dataRequests) {
|
||||
this._dataRequests = this._descriptor.__dataRequests.map(dataRequest => new DataRequest(dataRequest));
|
||||
} else {
|
||||
|
@ -196,7 +195,7 @@ export class AbstractLayer {
|
|||
return false;
|
||||
}
|
||||
|
||||
getLegendDetails() {
|
||||
renderLegendDetails() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -395,6 +394,10 @@ export class AbstractLayer {
|
|||
return [];
|
||||
}
|
||||
|
||||
async getOrdinalFields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
syncVisibilityWithMb(mbMap, mbLayerId) {
|
||||
mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none');
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
import { AbstractVectorSource } from '../vector_source';
|
||||
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
|
||||
import React from 'react';
|
||||
import { EMS_FILE, FEATURE_ID_PROPERTY_NAME } from '../../../../common/constants';
|
||||
import { EMS_FILE, FEATURE_ID_PROPERTY_NAME, FIELD_ORIGIN } from '../../../../common/constants';
|
||||
import { getEMSClient } from '../../../meta';
|
||||
import { EMSFileCreateSourceEditor } from './create_source_editor';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
import { TooltipProperty } from '../../tooltips/tooltip_property';
|
||||
import { EMSFileField } from '../../fields/ems_file_field';
|
||||
|
||||
export class EMSFileSource extends AbstractVectorSource {
|
||||
|
||||
|
@ -45,19 +45,29 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(EMSFileSource.createDescriptor(descriptor), inspectorAdapters);
|
||||
this._tooltipFields = this._descriptor.tooltipProperties.map(propertyKey => this.createField({ fieldName: propertyKey }));
|
||||
}
|
||||
|
||||
createField({ fieldName }) {
|
||||
return new EMSFileField({
|
||||
fieldName,
|
||||
source: this,
|
||||
origin: FIELD_ORIGIN.SOURCE
|
||||
});
|
||||
}
|
||||
|
||||
renderSourceSettingsEditor({ onChange }) {
|
||||
return (
|
||||
<UpdateSourceEditor
|
||||
onChange={onChange}
|
||||
tooltipProperties={this._descriptor.tooltipProperties}
|
||||
tooltipFields={this._tooltipFields}
|
||||
layerId={this._descriptor.id}
|
||||
source={this}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
async _getEMSFileLayer() {
|
||||
async getEMSFileLayer() {
|
||||
const emsClient = getEMSClient();
|
||||
const emsFileLayers = await emsClient.getFileLayers();
|
||||
const emsFileLayer = emsFileLayers.find((fileLayer => fileLayer.getId() === this._descriptor.id));
|
||||
|
@ -73,7 +83,7 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getGeoJsonWithMeta() {
|
||||
const emsFileLayer = await this._getEMSFileLayer();
|
||||
const emsFileLayer = await this.getEMSFileLayer();
|
||||
const featureCollection = await AbstractVectorSource.getGeoJson({
|
||||
format: emsFileLayer.getDefaultFormatType(),
|
||||
featureCollectionPath: 'data',
|
||||
|
@ -98,7 +108,7 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
async getImmutableProperties() {
|
||||
let emsLink;
|
||||
try {
|
||||
const emsFileLayer = await this._getEMSFileLayer();
|
||||
const emsFileLayer = await this.getEMSFileLayer();
|
||||
emsLink = emsFileLayer.getEMSHotLink();
|
||||
} catch(error) {
|
||||
// ignore error if EMS layer id could not be found
|
||||
|
@ -121,7 +131,7 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
|
||||
async getDisplayName() {
|
||||
try {
|
||||
const emsFileLayer = await this._getEMSFileLayer();
|
||||
const emsFileLayer = await this.getEMSFileLayer();
|
||||
return emsFileLayer.getDisplayName();
|
||||
} catch (error) {
|
||||
return this._descriptor.id;
|
||||
|
@ -129,36 +139,28 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getAttributions() {
|
||||
const emsFileLayer = await this._getEMSFileLayer();
|
||||
const emsFileLayer = await this.getEMSFileLayer();
|
||||
return emsFileLayer.getAttributions();
|
||||
}
|
||||
|
||||
|
||||
async getLeftJoinFields() {
|
||||
const emsFileLayer = await this._getEMSFileLayer();
|
||||
const emsFileLayer = await this.getEMSFileLayer();
|
||||
const fields = emsFileLayer.getFieldsInLanguage();
|
||||
return fields.map(f => {
|
||||
return { name: f.name, label: f.description };
|
||||
});
|
||||
return fields.map(f => this.createField({ fieldName: f.name }));
|
||||
}
|
||||
|
||||
canFormatFeatureProperties() {
|
||||
return this._descriptor.tooltipProperties.length;
|
||||
return this._tooltipFields.length > 0;
|
||||
}
|
||||
|
||||
async filterAndFormatPropertiesToHtml(properties) {
|
||||
const emsFileLayer = await this._getEMSFileLayer();
|
||||
const emsFields = emsFileLayer.getFieldsInLanguage();
|
||||
|
||||
return this._descriptor.tooltipProperties.map(propertyName => {
|
||||
// Map EMS field name to language specific label
|
||||
const emsField = emsFields.find(field => {
|
||||
return field.name === propertyName;
|
||||
});
|
||||
const label = emsField ? emsField.description : propertyName;
|
||||
|
||||
return new TooltipProperty(propertyName, label, properties[propertyName]);
|
||||
const tooltipProperties = this._tooltipFields.map(field => {
|
||||
const value = properties[field.getName()];
|
||||
return field.createTooltipProperty(value);
|
||||
});
|
||||
|
||||
return Promise.all(tooltipProperties);
|
||||
}
|
||||
|
||||
async getSupportedShapeTypes() {
|
||||
|
|
|
@ -13,7 +13,7 @@ function makeEMSFileSource(tooltipProperties) {
|
|||
const emsFileSource = new EMSFileSource({
|
||||
tooltipProperties: tooltipProperties
|
||||
});
|
||||
emsFileSource._getEMSFileLayer = () => {
|
||||
emsFileSource.getEMSFileLayer = () => {
|
||||
return {
|
||||
getFieldsInLanguage() {
|
||||
return [{
|
||||
|
|
|
@ -13,7 +13,8 @@ export class UpdateSourceEditor extends Component {
|
|||
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
tooltipProperties: PropTypes.arrayOf(PropTypes.string).isRequired
|
||||
tooltipFields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
source: PropTypes.object
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -36,16 +37,12 @@ export class UpdateSourceEditor extends Component {
|
|||
const emsFiles = await emsClient.getFileLayers();
|
||||
const emsFile = emsFiles.find((emsFile => emsFile.getId() === this.props.layerId));
|
||||
const emsFields = emsFile.getFieldsInLanguage();
|
||||
fields = emsFields.map(field => {
|
||||
return {
|
||||
name: field.name,
|
||||
label: field.description
|
||||
};
|
||||
});
|
||||
fields = emsFields.map(field => this.props.source.createField({ fieldName: field.name }));
|
||||
} catch(e) {
|
||||
//swallow this error. when a matching EMS-config cannot be found, the source already will have thrown errors during the data request. This will propagate to the vector-layer and be displayed in the UX
|
||||
fields = [];
|
||||
}
|
||||
|
||||
if (this._isMounted) {
|
||||
this.setState({ fields: fields });
|
||||
}
|
||||
|
@ -56,9 +53,10 @@ export class UpdateSourceEditor extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<TooltipSelector
|
||||
tooltipProperties={this.props.tooltipProperties}
|
||||
tooltipFields={this.props.tooltipFields}
|
||||
onChange={this._onTooltipPropertiesSelect}
|
||||
fields={this.state.fields}
|
||||
/>
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
*/
|
||||
|
||||
import { AbstractESSource } from './es_source';
|
||||
import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property';
|
||||
import { METRIC_TYPE } from '../../../common/constants';
|
||||
import _ from 'lodash';
|
||||
import { ESAggMetricField } from '../fields/es_agg_field';
|
||||
import { ESDocField } from '../fields/es_doc_field';
|
||||
import { METRIC_TYPE, COUNT_AGG_TYPE, COUNT_PROP_LABEL, COUNT_PROP_NAME, FIELD_ORIGIN } from '../../../common/constants';
|
||||
|
||||
const COUNT_PROP_LABEL = 'count';
|
||||
const COUNT_PROP_NAME = 'doc_count';
|
||||
const AGG_DELIMITER = '_of_';
|
||||
|
||||
export class AbstractESAggSource extends AbstractESSource {
|
||||
|
@ -34,109 +32,106 @@ export class AbstractESAggSource extends AbstractESSource {
|
|||
]
|
||||
};
|
||||
|
||||
_formatMetricKey(metric) {
|
||||
const aggType = metric.type;
|
||||
const fieldName = metric.field;
|
||||
return aggType !== METRIC_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME;
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(descriptor, inspectorAdapters);
|
||||
this._metricFields = this._descriptor.metrics ? this._descriptor.metrics.map(metric => {
|
||||
const esDocField = metric.field ? new ESDocField({ fieldName: metric.field, source: this }) : null;
|
||||
return new ESAggMetricField({
|
||||
label: metric.label,
|
||||
esDocField: esDocField,
|
||||
aggType: metric.type,
|
||||
source: this,
|
||||
origin: this.getOriginForField()
|
||||
});
|
||||
}) : [];
|
||||
}
|
||||
|
||||
_formatMetricLabel(metric) {
|
||||
const aggType = metric.type;
|
||||
const fieldName = metric.field;
|
||||
return aggType !== METRIC_TYPE.COUNT ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL;
|
||||
}
|
||||
createField({ fieldName, label }) {
|
||||
|
||||
_getValidMetrics() {
|
||||
const metrics = _.get(this._descriptor, 'metrics', []).filter(({ type, field }) => {
|
||||
if (type === METRIC_TYPE.COUNT) {
|
||||
return true;
|
||||
//if there is a corresponding field with a custom label, use that one.
|
||||
if (!label) {
|
||||
const matchField = this._metricFields.find(field => field.getName() === fieldName);
|
||||
if (matchField) {
|
||||
label = matchField.getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
if (field) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (fieldName === COUNT_PROP_NAME) {
|
||||
return new ESAggMetricField({
|
||||
aggType: COUNT_AGG_TYPE,
|
||||
label: label,
|
||||
source: this,
|
||||
origin: this.getOriginForField()
|
||||
});
|
||||
}
|
||||
//this only works because aggType is a fixed set and does not include the `_of_` string
|
||||
const [aggType, docField] = fieldName.split(AGG_DELIMITER);
|
||||
const esDocField = new ESDocField({ fieldName: docField, source: this });
|
||||
return new ESAggMetricField({
|
||||
label: label,
|
||||
esDocField,
|
||||
aggType,
|
||||
source: this,
|
||||
origin: this.getOriginForField()
|
||||
});
|
||||
}
|
||||
|
||||
getMetricFieldForName(fieldName) {
|
||||
return this.getMetricFields().find(metricField => {
|
||||
return metricField.getName() === fieldName;
|
||||
});
|
||||
}
|
||||
|
||||
getOriginForField() {
|
||||
return FIELD_ORIGIN.SOURCE;
|
||||
}
|
||||
|
||||
getMetricFields() {
|
||||
const metrics = this._metricFields.filter(esAggField => esAggField.isValid());
|
||||
if (metrics.length === 0) {
|
||||
metrics.push({ type: METRIC_TYPE.COUNT });
|
||||
metrics.push(new ESAggMetricField({
|
||||
aggType: COUNT_AGG_TYPE,
|
||||
source: this,
|
||||
origin: this.getOriginForField()
|
||||
}));
|
||||
}
|
||||
return metrics;
|
||||
}
|
||||
|
||||
getMetricFields() {
|
||||
return this._getValidMetrics().map(metric => {
|
||||
const metricKey = this._formatMetricKey(metric);
|
||||
const metricLabel = metric.label ? metric.label : this._formatMetricLabel(metric);
|
||||
const metricCopy = { ...metric };
|
||||
delete metricCopy.label;
|
||||
return {
|
||||
...metricCopy,
|
||||
propertyKey: metricKey,
|
||||
propertyLabel: metricLabel
|
||||
};
|
||||
});
|
||||
formatMetricKey(aggType, fieldName) {
|
||||
return aggType !== COUNT_AGG_TYPE ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME;
|
||||
}
|
||||
|
||||
async getNumberFields() {
|
||||
return this.getMetricFields().map(({ propertyKey: name, propertyLabel: label }) => {
|
||||
return { label, name };
|
||||
});
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
return this.getMetricFields().map(({ propertyKey }) => {
|
||||
return propertyKey;
|
||||
});
|
||||
formatMetricLabel(aggType, fieldName) {
|
||||
return aggType !== COUNT_AGG_TYPE ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL;
|
||||
}
|
||||
|
||||
createMetricAggConfigs() {
|
||||
return this.getMetricFields().map(metric => {
|
||||
const metricAggConfig = {
|
||||
id: metric.propertyKey,
|
||||
enabled: true,
|
||||
type: metric.type,
|
||||
schema: 'metric',
|
||||
params: {}
|
||||
};
|
||||
if (metric.type !== METRIC_TYPE.COUNT) {
|
||||
metricAggConfig.params = { field: metric.field };
|
||||
}
|
||||
return metricAggConfig;
|
||||
});
|
||||
return this.getMetricFields().map(esAggMetric => esAggMetric.makeMetricAggConfig());
|
||||
}
|
||||
|
||||
|
||||
async getNumberFields() {
|
||||
return this.getMetricFields();
|
||||
}
|
||||
|
||||
async filterAndFormatPropertiesToHtmlForMetricFields(properties) {
|
||||
let indexPattern;
|
||||
try {
|
||||
indexPattern = await this._getIndexPattern();
|
||||
} catch(error) {
|
||||
console.warn(`Unable to find Index pattern ${this._descriptor.indexPatternId}, values are not formatted`);
|
||||
return properties;
|
||||
}
|
||||
|
||||
const metricFields = this.getMetricFields();
|
||||
const tooltipProperties = [];
|
||||
const tooltipPropertiesPromises = [];
|
||||
metricFields.forEach((metricField) => {
|
||||
let value;
|
||||
for (const key in properties) {
|
||||
if (properties.hasOwnProperty(key) && metricField.propertyKey === key) {
|
||||
if (properties.hasOwnProperty(key) && metricField.getName() === key) {
|
||||
value = properties[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const tooltipProperty = new ESAggMetricTooltipProperty(
|
||||
metricField.propertyKey,
|
||||
metricField.propertyLabel,
|
||||
value,
|
||||
indexPattern,
|
||||
metricField
|
||||
);
|
||||
tooltipProperties.push(tooltipProperty);
|
||||
const tooltipPromise = metricField.createTooltipProperty(value);
|
||||
tooltipPropertiesPromises.push(tooltipPromise);
|
||||
});
|
||||
|
||||
return tooltipProperties;
|
||||
|
||||
return await Promise.all(tooltipPropertiesPromises);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,13 +20,12 @@ import { RENDER_AS } from './render_as';
|
|||
import { CreateSourceEditor } from './create_source_editor';
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
import { GRID_RESOLUTION } from '../../grid_resolution';
|
||||
import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID } from '../../../../common/constants';
|
||||
import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID, COUNT_PROP_LABEL, COUNT_PROP_NAME } from '../../../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { AbstractESAggSource } from '../es_agg_source';
|
||||
import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
|
||||
|
||||
const COUNT_PROP_LABEL = 'count';
|
||||
const COUNT_PROP_NAME = 'doc_count';
|
||||
const MAX_GEOTILE_LEVEL = 29;
|
||||
|
||||
const aggSchemas = new Schemas([
|
||||
|
@ -93,7 +92,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
async getImmutableProperties() {
|
||||
let indexPatternTitle = this._descriptor.indexPatternId;
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
indexPatternTitle = indexPattern.title;
|
||||
} catch (error) {
|
||||
// ignore error, title will just default to id
|
||||
|
@ -124,6 +123,10 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
];
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
return this.getMetricFields().map((esAggMetricField => esAggMetricField.getName()));
|
||||
}
|
||||
|
||||
isGeoGridPrecisionAware() {
|
||||
return true;
|
||||
}
|
||||
|
@ -163,7 +166,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
}
|
||||
|
||||
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const searchSource = await this._makeSearchSource(searchFilters, 0);
|
||||
const aggConfigs = new AggConfigs(indexPattern, this._makeAggConfigs(searchFilters.geogridPrecision), aggSchemas.all);
|
||||
searchSource.setField('aggs', aggConfigs.toDsl());
|
||||
|
@ -225,7 +228,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
});
|
||||
descriptor.style = VectorStyle.createDescriptor({
|
||||
[vectorStyles.FILL_COLOR]: {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
type: DynamicStyleProperty.type,
|
||||
options: {
|
||||
field: {
|
||||
label: COUNT_PROP_LABEL,
|
||||
|
@ -236,7 +239,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
}
|
||||
},
|
||||
[vectorStyles.ICON_SIZE]: {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
type: DynamicStyleProperty.type,
|
||||
options: {
|
||||
field: {
|
||||
label: COUNT_PROP_LABEL,
|
||||
|
|
|
@ -14,15 +14,14 @@ import { UpdateSourceEditor } from './update_source_editor';
|
|||
import { VectorStyle } from '../../styles/vector/vector_style';
|
||||
import { vectorStyles } from '../../styles/vector/vector_style_defaults';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW } from '../../../../common/constants';
|
||||
import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW, COUNT_PROP_NAME, COUNT_PROP_LABEL } from '../../../../common/constants';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { convertToLines } from './convert_to_lines';
|
||||
import { Schemas } from 'ui/vis/editors/default/schemas';
|
||||
import { AggConfigs } from 'ui/agg_types';
|
||||
import { AbstractESAggSource } from '../es_agg_source';
|
||||
import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
|
||||
|
||||
const COUNT_PROP_LABEL = 'count';
|
||||
const COUNT_PROP_NAME = 'doc_count';
|
||||
const MAX_GEOTILE_LEVEL = 29;
|
||||
|
||||
const aggSchemas = new Schemas([AbstractESAggSource.METRIC_SCHEMA_CONFIG]);
|
||||
|
@ -92,7 +91,7 @@ export class ESPewPewSource extends AbstractESAggSource {
|
|||
async getImmutableProperties() {
|
||||
let indexPatternTitle = this._descriptor.indexPatternId;
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
indexPatternTitle = indexPattern.title;
|
||||
} catch (error) {
|
||||
// ignore error, title will just default to id
|
||||
|
@ -126,7 +125,7 @@ export class ESPewPewSource extends AbstractESAggSource {
|
|||
createDefaultLayer(options) {
|
||||
const styleDescriptor = VectorStyle.createDescriptor({
|
||||
[vectorStyles.LINE_COLOR]: {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
type: DynamicStyleProperty.type,
|
||||
options: {
|
||||
field: {
|
||||
label: COUNT_PROP_LABEL,
|
||||
|
@ -137,7 +136,7 @@ export class ESPewPewSource extends AbstractESAggSource {
|
|||
}
|
||||
},
|
||||
[vectorStyles.LINE_WIDTH]: {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
type: DynamicStyleProperty.type,
|
||||
options: {
|
||||
field: {
|
||||
label: COUNT_PROP_LABEL,
|
||||
|
@ -167,7 +166,7 @@ export class ESPewPewSource extends AbstractESAggSource {
|
|||
}
|
||||
|
||||
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const metricAggConfigs = this.createMetricAggConfigs();
|
||||
const aggConfigs = new AggConfigs(indexPattern, metricAggConfigs, aggSchemas.all);
|
||||
|
||||
|
@ -223,7 +222,7 @@ export class ESPewPewSource extends AbstractESAggSource {
|
|||
}
|
||||
|
||||
async _getGeoField() {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const geoField = indexPattern.fields.getByName(this._descriptor.destGeoField);
|
||||
if (!geoField) {
|
||||
throw new Error(i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', {
|
||||
|
|
|
@ -13,7 +13,7 @@ exports[`should enable sort order select when sort field provided 1`] = `
|
|||
<TooltipSelector
|
||||
fields={null}
|
||||
onChange={[Function]}
|
||||
tooltipProperties={Array []}
|
||||
tooltipFields={Array []}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
|
@ -115,7 +115,7 @@ exports[`should render top hits form when useTopHits is true 1`] = `
|
|||
<TooltipSelector
|
||||
fields={null}
|
||||
onChange={[Function]}
|
||||
tooltipProperties={Array []}
|
||||
tooltipFields={Array []}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
|
@ -256,7 +256,7 @@ exports[`should render update source editor 1`] = `
|
|||
<TooltipSelector
|
||||
fields={null}
|
||||
onChange={[Function]}
|
||||
tooltipProperties={Array []}
|
||||
tooltipFields={Array []}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
|
|
|
@ -22,10 +22,10 @@ import {
|
|||
} from '../../../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { ESTooltipProperty } from '../../tooltips/es_tooltip_property';
|
||||
import { getSourceFields } from '../../../index_pattern_util';
|
||||
|
||||
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
|
||||
import { ESDocField } from '../../fields/es_doc_field';
|
||||
|
||||
export class ESSearchSource extends AbstractESSource {
|
||||
|
||||
|
@ -68,15 +68,25 @@ export class ESSearchSource extends AbstractESSource {
|
|||
topHitsSplitField: descriptor.topHitsSplitField,
|
||||
topHitsSize: _.get(descriptor, 'topHitsSize', 1),
|
||||
}, inspectorAdapters);
|
||||
|
||||
this._tooltipFields = this._descriptor.tooltipProperties.map((property) => this.createField({ fieldName: property }));
|
||||
}
|
||||
|
||||
createField({ fieldName }) {
|
||||
return new ESDocField({
|
||||
fieldName,
|
||||
source: this
|
||||
});
|
||||
}
|
||||
|
||||
renderSourceSettingsEditor({ onChange }) {
|
||||
return (
|
||||
<UpdateSourceEditor
|
||||
source={this}
|
||||
indexPatternId={this._descriptor.indexPatternId}
|
||||
onChange={onChange}
|
||||
filterByMapBounds={this._descriptor.filterByMapBounds}
|
||||
tooltipProperties={this._descriptor.tooltipProperties}
|
||||
tooltipFields={this._tooltipFields}
|
||||
sortField={this._descriptor.sortField}
|
||||
sortOrder={this._descriptor.sortOrder}
|
||||
useTopHits={this._descriptor.useTopHits}
|
||||
|
@ -89,9 +99,9 @@ export class ESSearchSource extends AbstractESSource {
|
|||
|
||||
async getNumberFields() {
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
return indexPattern.fields.getByType('number').map(field => {
|
||||
return { name: field.name, label: field.name };
|
||||
return this.createField({ fieldName: field.name });
|
||||
});
|
||||
} catch (error) {
|
||||
return [];
|
||||
|
@ -100,19 +110,15 @@ export class ESSearchSource extends AbstractESSource {
|
|||
|
||||
async getDateFields() {
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
return indexPattern.fields.getByType('date').map(field => {
|
||||
return { name: field.name, label: field.name };
|
||||
return this.createField({ fieldName: field.name });
|
||||
});
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getMetricFields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
return [this._descriptor.geoField];
|
||||
}
|
||||
|
@ -121,7 +127,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
let indexPatternTitle = this._descriptor.indexPatternId;
|
||||
let geoFieldType = '';
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
indexPatternTitle = indexPattern.title;
|
||||
const geoField = await this._getGeoField();
|
||||
geoFieldType = geoField.type;
|
||||
|
@ -171,7 +177,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
async _excludeDateFields(fieldNames) {
|
||||
const dateFieldNames = _.map(await this.getDateFields(), 'name');
|
||||
const dateFieldNames = (await this.getDateFields()).map(field => field.getName());
|
||||
return fieldNames.filter(field => {
|
||||
return !dateFieldNames.includes(field);
|
||||
});
|
||||
|
@ -179,7 +185,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
|
||||
// Returns docvalue_fields array for the union of indexPattern's dateFields and request's field names.
|
||||
async _getDateDocvalueFields(searchFields) {
|
||||
const dateFieldNames = _.map(await this.getDateFields(), 'name');
|
||||
const dateFieldNames = (await this.getDateFields()).map(field => field.getName());
|
||||
return searchFields
|
||||
.filter(fieldName => {
|
||||
return dateFieldNames.includes(fieldName);
|
||||
|
@ -198,7 +204,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
topHitsSize,
|
||||
} = this._descriptor;
|
||||
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const geoField = await this._getGeoField();
|
||||
|
||||
const scriptFields = {};
|
||||
|
@ -329,7 +335,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
? await this._getTopHits(layerName, searchFilters, registerCancelCallback)
|
||||
: await this._getSearchHits(layerName, searchFilters, registerCancelCallback);
|
||||
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const unusedMetaFields = indexPattern.metaFields.filter(metaField => {
|
||||
return !['_id', '_index'].includes(metaField);
|
||||
});
|
||||
|
@ -362,11 +368,11 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
canFormatFeatureProperties() {
|
||||
return this._descriptor.tooltipProperties.length > 0;
|
||||
return this._tooltipFields.length > 0;
|
||||
}
|
||||
|
||||
async _loadTooltipProperties(docId, index, indexPattern) {
|
||||
if (this._descriptor.tooltipProperties.length === 0) {
|
||||
if (this._tooltipFields.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -378,7 +384,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
query: `_id:"${docId}" and _index:${index}`
|
||||
};
|
||||
searchSource.setField('query', query);
|
||||
searchSource.setField('fields', this._descriptor.tooltipProperties);
|
||||
searchSource.setField('fields', this._getTooltipPropertyNames());
|
||||
|
||||
const resp = await searchSource.fetch();
|
||||
|
||||
|
@ -394,7 +400,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
|
||||
const properties = indexPattern.flattenHit(hit);
|
||||
indexPattern.metaFields.forEach(metaField => {
|
||||
if (!this._descriptor.tooltipProperties.includes(metaField)) {
|
||||
if (!this._getTooltipPropertyNames().includes(metaField)) {
|
||||
delete properties[metaField];
|
||||
}
|
||||
});
|
||||
|
@ -402,12 +408,13 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
async filterAndFormatPropertiesToHtml(properties) {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const propertyValues = await this._loadTooltipProperties(properties._id, properties._index, indexPattern);
|
||||
|
||||
return this._descriptor.tooltipProperties.map(propertyName => {
|
||||
return new ESTooltipProperty(propertyName, propertyName, propertyValues[propertyName], indexPattern);
|
||||
const tooltipProperties = this._tooltipFields.map(field => {
|
||||
const value = propertyValues[field.getName()];
|
||||
return field.createTooltipProperty(value);
|
||||
});
|
||||
return Promise.all(tooltipProperties);
|
||||
}
|
||||
|
||||
isFilterByMapBounds() {
|
||||
|
@ -415,12 +422,9 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
async getLeftJoinFields() {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
// Left fields are retrieved from _source.
|
||||
return getSourceFields(indexPattern.fields)
|
||||
.map(field => {
|
||||
return { name: field.name, label: field.name };
|
||||
});
|
||||
return getSourceFields(indexPattern.fields).map(field => this.createField({ fieldName: field.name }));
|
||||
}
|
||||
|
||||
async getSupportedShapeTypes() {
|
||||
|
@ -507,7 +511,6 @@ export class ESSearchSource extends AbstractESSource {
|
|||
|
||||
async getPreIndexedShape(properties) {
|
||||
const geoField = await this._getGeoField();
|
||||
|
||||
return {
|
||||
index: properties._index, // Can not use index pattern title because it may reference many indices
|
||||
id: properties._id,
|
||||
|
|
|
@ -22,22 +22,24 @@ import { i18n } from '@kbn/i18n';
|
|||
import { getTermsFields, getSourceFields } from '../../../index_pattern_util';
|
||||
import { ValidatedRange } from '../../../components/validated_range';
|
||||
import { SORT_ORDER } from '../../../../common/constants';
|
||||
import { ESDocField } from '../../fields/es_doc_field';
|
||||
|
||||
export class UpdateSourceEditor extends Component {
|
||||
static propTypes = {
|
||||
indexPatternId: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
filterByMapBounds: PropTypes.bool.isRequired,
|
||||
tooltipProperties: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
tooltipFields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortField: PropTypes.string,
|
||||
sortOrder: PropTypes.string.isRequired,
|
||||
useTopHits: PropTypes.bool.isRequired,
|
||||
topHitsSplitField: PropTypes.string,
|
||||
topHitsSize: PropTypes.number.isRequired,
|
||||
source: PropTypes.object
|
||||
};
|
||||
|
||||
state = {
|
||||
tooltipFields: null,
|
||||
sourceFields: null,
|
||||
termFields: null,
|
||||
sortFields: null,
|
||||
};
|
||||
|
@ -73,10 +75,19 @@ export class UpdateSourceEditor extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
//todo move this all to the source
|
||||
const rawTooltipFields = getSourceFields(indexPattern.fields);
|
||||
const sourceFields = rawTooltipFields.map(field => {
|
||||
return new ESDocField({
|
||||
fieldName: field.name,
|
||||
source: this.props.source
|
||||
});
|
||||
});
|
||||
|
||||
this.setState({
|
||||
tooltipFields: getSourceFields(indexPattern.fields),
|
||||
termFields: getTermsFields(indexPattern.fields),
|
||||
sortFields: indexPattern.fields.filter(field => field.sortable),
|
||||
sourceFields: sourceFields,
|
||||
termFields: getTermsFields(indexPattern.fields), //todo change term fields to use fields
|
||||
sortFields: indexPattern.fields.filter(field => field.sortable), //todo change sort fields to use fields
|
||||
});
|
||||
}
|
||||
_onTooltipPropertiesChange = propertyNames => {
|
||||
|
@ -173,9 +184,9 @@ export class UpdateSourceEditor extends Component {
|
|||
<Fragment>
|
||||
<EuiFormRow>
|
||||
<TooltipSelector
|
||||
tooltipProperties={this.props.tooltipProperties}
|
||||
tooltipFields={this.props.tooltipFields}
|
||||
onChange={this._onTooltipPropertiesChange}
|
||||
fields={this.state.tooltipFields}
|
||||
fields={this.state.sourceFields}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const defaultProps = {
|
|||
indexPatternId: 'indexPattern1',
|
||||
onChange: () => {},
|
||||
filterByMapBounds: true,
|
||||
tooltipProperties: [],
|
||||
tooltipFields: [],
|
||||
sortOrder: 'DESC',
|
||||
useTopHits: false,
|
||||
topHitsSplitField: 'trackId',
|
||||
|
|
|
@ -69,6 +69,10 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
return clonedDescriptor;
|
||||
}
|
||||
|
||||
getMetricFields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async _runEsQuery(requestName, searchSource, registerCancelCallback, requestDescription) {
|
||||
const abortController = new AbortController();
|
||||
registerCancelCallback(() => abortController.abort());
|
||||
|
@ -95,7 +99,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async _makeSearchSource(searchFilters, limit, initialSearchContext) {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const isTimeAware = await this.isTimeAware();
|
||||
const applyGlobalQuery = _.get(searchFilters, 'applyGlobalQuery', true);
|
||||
const globalFilters = applyGlobalQuery ? searchFilters.filters : [];
|
||||
|
@ -130,7 +134,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
|
||||
const searchSource = await this._makeSearchSource({ sourceQuery, query, timeFilters, filters, applyGlobalQuery }, 0);
|
||||
const geoField = await this._getGeoField();
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
|
||||
const geoBoundsAgg = [{
|
||||
type: 'geo_bounds',
|
||||
|
@ -171,7 +175,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
|
||||
async isTimeAware() {
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const timeField = indexPattern.timeFieldName;
|
||||
return !!timeField;
|
||||
} catch (error) {
|
||||
|
@ -179,7 +183,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
}
|
||||
}
|
||||
|
||||
async _getIndexPattern() {
|
||||
async getIndexPattern() {
|
||||
if (this.indexPattern) {
|
||||
return this.indexPattern;
|
||||
}
|
||||
|
@ -208,7 +212,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
|
||||
|
||||
async _getGeoField() {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const geoField = indexPattern.fields.getByName(this._descriptor.geoField);
|
||||
if (!geoField) {
|
||||
throw new Error(i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', {
|
||||
|
@ -221,7 +225,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
|
||||
async getDisplayName() {
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
return indexPattern.title;
|
||||
} catch (error) {
|
||||
// Unable to load index pattern, just return id as display name
|
||||
|
@ -238,25 +242,27 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getFieldFormatter(fieldName) {
|
||||
const metricField = this.getMetricFields().find(({ propertyKey }) => {
|
||||
return propertyKey === fieldName;
|
||||
});
|
||||
|
||||
const metricField = this.getMetricFields().find(field => field.getName() === fieldName);
|
||||
|
||||
// Do not use field formatters for counting metrics
|
||||
if (metricField && metricField.type === METRIC_TYPE.COUNT || metricField.type === METRIC_TYPE.UNIQUE_COUNT) {
|
||||
if (metricField && (metricField.type === METRIC_TYPE.COUNT || metricField.type === METRIC_TYPE.UNIQUE_COUNT)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// fieldName could be an aggregation so it needs to be unpacked to expose raw field.
|
||||
const realFieldName = metricField ? metricField.getESDocFieldName() : fieldName;
|
||||
if (!realFieldName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let indexPattern;
|
||||
try {
|
||||
indexPattern = await this._getIndexPattern();
|
||||
indexPattern = await this.getIndexPattern();
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const realFieldName = metricField
|
||||
? metricField.field
|
||||
: fieldName;
|
||||
const fieldFromIndexPattern = indexPattern.fields.getByName(realFieldName);
|
||||
if (!fieldFromIndexPattern) {
|
||||
return null;
|
||||
|
@ -264,4 +270,5 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
|
||||
return fieldFromIndexPattern.format.getConverterFor('text');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,12 +9,15 @@ import _ from 'lodash';
|
|||
import { Schemas } from 'ui/vis/editors/default/schemas';
|
||||
import { AggConfigs } from 'ui/agg_types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ESTooltipProperty } from '../tooltips/es_tooltip_property';
|
||||
import { ES_SIZE_LIMIT, METRIC_TYPE } from '../../../common/constants';
|
||||
import { ES_SIZE_LIMIT, FIELD_ORIGIN, METRIC_TYPE } from '../../../common/constants';
|
||||
import { ESDocField } from '../fields/es_doc_field';
|
||||
import { AbstractESAggSource } from './es_agg_source';
|
||||
|
||||
const TERMS_AGG_NAME = 'join';
|
||||
|
||||
const FIELD_NAME_PREFIX = '__kbnjoin__';
|
||||
const GROUP_BY_DELIMITER = '_groupby_';
|
||||
|
||||
const aggSchemas = new Schemas([
|
||||
AbstractESAggSource.METRIC_SCHEMA_CONFIG,
|
||||
{
|
||||
|
@ -48,6 +51,10 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
|
||||
static type = 'ES_TERM_SOURCE';
|
||||
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(descriptor, inspectorAdapters);
|
||||
this._termField = new ESDocField({ fieldName: descriptor.term, source: this, origin: this.getOriginForField() });
|
||||
}
|
||||
|
||||
static renderEditor({}) {
|
||||
//no need to localize. this editor is never rendered.
|
||||
|
@ -62,22 +69,26 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
return [this._descriptor.indexPatternId];
|
||||
}
|
||||
|
||||
getTerm() {
|
||||
return this._descriptor.term;
|
||||
getTermField() {
|
||||
return this._termField;
|
||||
}
|
||||
|
||||
getOriginForField() {
|
||||
return FIELD_ORIGIN.JOIN;
|
||||
}
|
||||
|
||||
getWhereQuery() {
|
||||
return this._descriptor.whereQuery;
|
||||
}
|
||||
|
||||
_formatMetricKey(metric) {
|
||||
const metricKey = metric.type !== METRIC_TYPE.COUNT ? `${metric.type}_of_${metric.field}` : metric.type;
|
||||
return `__kbnjoin__${metricKey}_groupby_${this._descriptor.indexPatternTitle}.${this._descriptor.term}`;
|
||||
formatMetricKey(aggType, fieldName) {
|
||||
const metricKey = aggType !== METRIC_TYPE.COUNT ? `${aggType}_of_${fieldName}` : aggType;
|
||||
return `${FIELD_NAME_PREFIX}${metricKey}${GROUP_BY_DELIMITER}${this._descriptor.indexPatternTitle}.${this._termField.getName()}`;
|
||||
}
|
||||
|
||||
_formatMetricLabel(metric) {
|
||||
const metricLabel = metric.type !== METRIC_TYPE.COUNT ? `${metric.type} ${metric.field}` : 'count';
|
||||
return `${metricLabel} of ${this._descriptor.indexPatternTitle}:${this._descriptor.term}`;
|
||||
formatMetricLabel(type, fieldName) {
|
||||
const metricLabel = type !== METRIC_TYPE.COUNT ? `${type} ${fieldName}` : 'count';
|
||||
return `${metricLabel} of ${this._descriptor.indexPatternTitle}:${this._termField.getName()}`;
|
||||
}
|
||||
|
||||
async getPropertiesMap(searchFilters, leftSourceName, leftFieldName, registerCancelCallback) {
|
||||
|
@ -86,13 +97,13 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
return [];
|
||||
}
|
||||
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const searchSource = await this._makeSearchSource(searchFilters, 0);
|
||||
const configStates = this._makeAggConfigs();
|
||||
const aggConfigs = new AggConfigs(indexPattern, configStates, aggSchemas.all);
|
||||
searchSource.setField('aggs', aggConfigs.toDsl());
|
||||
|
||||
const requestName = `${this._descriptor.indexPatternTitle}.${this._descriptor.term}`;
|
||||
const requestName = `${this._descriptor.indexPatternTitle}.${this._termField.getName()}`;
|
||||
const requestDesc = this._getRequestDescription(leftSourceName, leftFieldName);
|
||||
const rawEsData = await this._runEsQuery(requestName, searchSource, registerCancelCallback, requestDesc);
|
||||
|
||||
|
@ -117,15 +128,13 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
}
|
||||
|
||||
_getRequestDescription(leftSourceName, leftFieldName) {
|
||||
const metrics = this._getValidMetrics().map(metric => {
|
||||
return metric.type !== METRIC_TYPE.COUNT ? `${metric.type} ${metric.field}` : 'count';
|
||||
});
|
||||
const metrics = this.getMetricFields().map(esAggMetric => esAggMetric.getRequestDescription());
|
||||
const joinStatement = [];
|
||||
joinStatement.push(i18n.translate('xpack.maps.source.esJoin.joinLeftDescription', {
|
||||
defaultMessage: `Join {leftSourceName}:{leftFieldName} with`,
|
||||
values: { leftSourceName, leftFieldName }
|
||||
}));
|
||||
joinStatement.push(`${this._descriptor.indexPatternTitle}:${this._descriptor.term}`);
|
||||
joinStatement.push(`${this._descriptor.indexPatternTitle}:${this._termField.getName()}`);
|
||||
joinStatement.push(i18n.translate('xpack.maps.source.esJoin.joinMetricsDescription', {
|
||||
defaultMessage: `for metrics {metrics}`,
|
||||
values: { metrics: metrics.join(',') }
|
||||
|
@ -148,7 +157,7 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
type: 'terms',
|
||||
schema: 'segment',
|
||||
params: {
|
||||
field: this._descriptor.term,
|
||||
field: this._termField.getName(),
|
||||
size: ES_SIZE_LIMIT
|
||||
}
|
||||
}
|
||||
|
@ -164,15 +173,7 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
return await this.filterAndFormatPropertiesToHtmlForMetricFields(properties);
|
||||
}
|
||||
|
||||
async createESTooltipProperty(propertyName, rawValue) {
|
||||
try {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
if (!indexPattern) {
|
||||
return null;
|
||||
}
|
||||
return new ESTooltipProperty(propertyName, propertyName, rawValue, indexPattern);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
getFieldNames() {
|
||||
return this.getMetricFields().map(esAggMetricField => esAggMetricField.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,21 +36,21 @@ const metricExamples = [
|
|||
|
||||
describe('getMetricFields', () => {
|
||||
|
||||
it('should add default "count" metric when no metrics are provided', () => {
|
||||
it('should add default "count" metric when no metrics are provided', async () => {
|
||||
const source = new ESTermSource({
|
||||
indexPatternTitle: indexPatternTitle,
|
||||
term: termFieldName,
|
||||
});
|
||||
const metrics = source.getMetricFields();
|
||||
expect(metrics.length).toBe(1);
|
||||
expect(metrics[0]).toEqual({
|
||||
type: 'count',
|
||||
propertyKey: '__kbnjoin__count_groupby_myIndex.myTermField',
|
||||
propertyLabel: 'count of myIndex:myTermField',
|
||||
});
|
||||
|
||||
expect(metrics[0].getAggType()).toEqual('count');
|
||||
expect(metrics[0].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField');
|
||||
expect(await metrics[0].getLabel()).toEqual('count of myIndex:myTermField');
|
||||
|
||||
});
|
||||
|
||||
it('should remove incomplete metric configurations', () => {
|
||||
it('should remove incomplete metric configurations', async () => {
|
||||
const source = new ESTermSource({
|
||||
indexPatternTitle: indexPatternTitle,
|
||||
term: termFieldName,
|
||||
|
@ -58,17 +58,16 @@ describe('getMetricFields', () => {
|
|||
});
|
||||
const metrics = source.getMetricFields();
|
||||
expect(metrics.length).toBe(2);
|
||||
expect(metrics[0]).toEqual({
|
||||
type: 'sum',
|
||||
field: sumFieldName,
|
||||
propertyKey: '__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField',
|
||||
propertyLabel: 'my custom label',
|
||||
});
|
||||
expect(metrics[1]).toEqual({
|
||||
type: 'count',
|
||||
propertyKey: '__kbnjoin__count_groupby_myIndex.myTermField',
|
||||
propertyLabel: 'count of myIndex:myTermField',
|
||||
});
|
||||
|
||||
expect(metrics[0].getAggType()).toEqual('sum');
|
||||
expect(metrics[0].getESDocFieldName()).toEqual(sumFieldName);
|
||||
expect(metrics[0].getName()).toEqual('__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField');
|
||||
expect(await metrics[0].getLabel()).toEqual('my custom label');
|
||||
|
||||
expect(metrics[1].getAggType()).toEqual('count');
|
||||
expect(metrics[1].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField');
|
||||
expect(await metrics[1].getLabel()).toEqual('count of myIndex:myTermField');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ import { CreateSourceEditor } from './create_source_editor';
|
|||
import { getKibanaRegionList } from '../../../meta';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { FEATURE_ID_PROPERTY_NAME } from '../../../../common/constants';
|
||||
import { FEATURE_ID_PROPERTY_NAME, FIELD_ORIGIN } from '../../../../common/constants';
|
||||
import { KibanaRegionField } from '../../fields/kibana_region_field';
|
||||
|
||||
export class KibanaRegionmapSource extends AbstractVectorSource {
|
||||
|
||||
|
@ -45,11 +46,20 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
);
|
||||
};
|
||||
|
||||
createField({ fieldName }) {
|
||||
return new KibanaRegionField({
|
||||
fieldName,
|
||||
source: this,
|
||||
origin: FIELD_ORIGIN.SOURCE
|
||||
});
|
||||
}
|
||||
|
||||
async getImmutableProperties() {
|
||||
return [
|
||||
{
|
||||
label: getDataSourceLabel(),
|
||||
value: KibanaRegionmapSource.title },
|
||||
value: KibanaRegionmapSource.title
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerLabel', {
|
||||
defaultMessage: 'Vector layer'
|
||||
|
@ -59,7 +69,7 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
];
|
||||
}
|
||||
|
||||
async _getVectorFileMeta() {
|
||||
async getVectorFileMeta() {
|
||||
const regionList = getKibanaRegionList();
|
||||
const meta = regionList.find(source => source.name === this._descriptor.name);
|
||||
if (!meta) {
|
||||
|
@ -75,7 +85,7 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getGeoJsonWithMeta() {
|
||||
const vectorFileMeta = await this._getVectorFileMeta();
|
||||
const vectorFileMeta = await this.getVectorFileMeta();
|
||||
const featureCollection = await AbstractVectorSource.getGeoJson({
|
||||
format: vectorFileMeta.format.type,
|
||||
featureCollectionPath: vectorFileMeta.meta.feature_collection_path,
|
||||
|
@ -90,10 +100,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getLeftJoinFields() {
|
||||
const vectorFileMeta = await this._getVectorFileMeta();
|
||||
return vectorFileMeta.fields.map(f => {
|
||||
return { name: f.name, label: f.description };
|
||||
});
|
||||
const vectorFileMeta = await this.getVectorFileMeta();
|
||||
return vectorFileMeta.fields.map(f => this.createField({ fieldName: f.name }));
|
||||
}
|
||||
|
||||
async getDisplayName() {
|
||||
|
|
|
@ -48,6 +48,10 @@ export class AbstractVectorSource extends AbstractSource {
|
|||
}));
|
||||
}
|
||||
|
||||
createField() {
|
||||
throw new Error(`Should implemement ${this.constructor.type} ${this}`);
|
||||
}
|
||||
|
||||
_createDefaultLayerDescriptor(options, mapColors) {
|
||||
return VectorLayer.createDescriptor(
|
||||
{
|
||||
|
@ -57,6 +61,10 @@ export class AbstractVectorSource extends AbstractSource {
|
|||
mapColors);
|
||||
}
|
||||
|
||||
_getTooltipPropertyNames() {
|
||||
return this._tooltipFields.map(field => field.getName());
|
||||
}
|
||||
|
||||
createDefaultLayer(options, mapColors) {
|
||||
const layerDescriptor = this._createDefaultLayerDescriptor(options, mapColors);
|
||||
const style = new VectorStyle(layerDescriptor.style, this);
|
||||
|
@ -131,4 +139,5 @@ export class AbstractVectorSource extends AbstractSource {
|
|||
getSourceTooltipContent(/* sourceDataRequest */) {
|
||||
return { tooltipContent: null, areResultsTrimmed: false };
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,26 +15,54 @@ import {
|
|||
HEATMAP_COLOR_RAMP_LABEL
|
||||
} from '../heatmap_constants';
|
||||
|
||||
export function HeatmapLegend({ colorRampName, label }) {
|
||||
const header = colorRampName === DEFAULT_HEATMAP_COLOR_RAMP_NAME
|
||||
? <ColorGradient colorRamp={DEFAULT_RGB_HEATMAP_COLOR_RAMP}/>
|
||||
: <ColorGradient colorRampName={colorRampName}/>;
|
||||
export class HeatmapLegend extends React.Component {
|
||||
|
||||
return (
|
||||
<StyleLegendRow
|
||||
header={header}
|
||||
minLabel={
|
||||
i18n.translate('xpack.maps.heatmapLegend.coldLabel', {
|
||||
defaultMessage: 'cold'
|
||||
})
|
||||
}
|
||||
maxLabel={
|
||||
i18n.translate('xpack.maps.heatmapLegend.hotLabel', {
|
||||
defaultMessage: 'hot'
|
||||
})
|
||||
}
|
||||
propertyLabel={HEATMAP_COLOR_RAMP_LABEL}
|
||||
fieldLabel={label}
|
||||
/>
|
||||
);
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { label: '' };
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._loadLabel();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadLabel();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
async _loadLabel() {
|
||||
const label = await this.props.field.getLabel();
|
||||
if (this._isMounted && this.state.label !== label) {
|
||||
this.setState({ label });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const colorRampName = this.props.colorRampName;
|
||||
const header = colorRampName === DEFAULT_HEATMAP_COLOR_RAMP_NAME
|
||||
? <ColorGradient colorRamp={DEFAULT_RGB_HEATMAP_COLOR_RAMP}/>
|
||||
: <ColorGradient colorRampName={colorRampName}/>;
|
||||
|
||||
return (
|
||||
<StyleLegendRow
|
||||
header={header}
|
||||
minLabel={
|
||||
i18n.translate('xpack.maps.heatmapLegend.coldLabel', {
|
||||
defaultMessage: 'cold'
|
||||
})
|
||||
}
|
||||
maxLabel={
|
||||
i18n.translate('xpack.maps.heatmapLegend.hotLabel', {
|
||||
defaultMessage: 'hot'
|
||||
})
|
||||
}
|
||||
propertyLabel={HEATMAP_COLOR_RAMP_LABEL}
|
||||
fieldLabel={this.state.label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,11 +50,11 @@ export class HeatmapStyle extends AbstractStyle {
|
|||
);
|
||||
}
|
||||
|
||||
getLegendDetails(label) {
|
||||
renderLegendDetails(field) {
|
||||
return (
|
||||
<HeatmapLegend
|
||||
colorRampName={this._descriptor.colorRampName}
|
||||
label={label}
|
||||
field={field}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,71 +5,13 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styleOptionShapes, rangeShape } from '../style_option_shapes';
|
||||
import { VectorStyle } from '../../vector_style';
|
||||
import { ColorGradient } from '../../../components/color_gradient';
|
||||
import { CircleIcon } from './circle_icon';
|
||||
import { rangeShape } from '../style_option_shapes';
|
||||
import { getVectorStyleLabel } from '../get_vector_style_label';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
|
||||
import { StyleLegendRow } from '../../../components/style_legend_row';
|
||||
|
||||
function getLineWidthIcons() {
|
||||
const defaultStyle = {
|
||||
stroke: 'grey',
|
||||
fill: 'none',
|
||||
width: '12px',
|
||||
};
|
||||
return [
|
||||
<CircleIcon style={{ ...defaultStyle, strokeWidth: '1px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, strokeWidth: '2px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, strokeWidth: '3px' }}/>,
|
||||
];
|
||||
}
|
||||
|
||||
function getSymbolSizeIcons() {
|
||||
const defaultStyle = {
|
||||
stroke: 'grey',
|
||||
strokeWidth: 'none',
|
||||
fill: 'grey',
|
||||
};
|
||||
return [
|
||||
<CircleIcon style={{ ...defaultStyle, width: '4px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, width: '8px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, width: '12px' }}/>,
|
||||
];
|
||||
}
|
||||
|
||||
function renderHeaderWithIcons(icons) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween" alignItems="center">
|
||||
{
|
||||
icons.map((icon, index) => {
|
||||
const isLast = index === icons.length - 1;
|
||||
let spacer;
|
||||
if (!isLast) {
|
||||
spacer = (
|
||||
<EuiFlexItem>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{icon}
|
||||
</EuiFlexItem>
|
||||
{spacer}
|
||||
</Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const EMPTY_VALUE = '';
|
||||
|
||||
export class StylePropertyLegendRow extends Component {
|
||||
|
@ -97,19 +39,25 @@ export class StylePropertyLegendRow extends Component {
|
|||
}
|
||||
|
||||
async _loadFieldFormatter() {
|
||||
this._fieldValueFormatter = await this.props.getFieldFormatter(this.props.options.field);
|
||||
if (this.props.style.isDynamic() && this.props.style.isComplete() && this.props.style.getField().getSource()) {
|
||||
const field = this.props.style.getField();
|
||||
const source = field.getSource();
|
||||
this._fieldValueFormatter = await source.getFieldFormatter(field.getName());
|
||||
} else {
|
||||
this._fieldValueFormatter = null;
|
||||
}
|
||||
if (this._isMounted) {
|
||||
this.setState({ hasLoadedFieldFormatter: true });
|
||||
}
|
||||
}
|
||||
|
||||
_loadLabel = async () => {
|
||||
if (this._isStatic()) {
|
||||
if (this._excludeFromHeader()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// have to load label and then check for changes since field name stays constant while label may change
|
||||
const label = await this.props.getFieldLabel(this.props.options.field.name);
|
||||
const label = await this.props.style.getField().getLabel();
|
||||
if (this._prevLabel === label) {
|
||||
return;
|
||||
}
|
||||
|
@ -120,9 +68,8 @@ export class StylePropertyLegendRow extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
_isStatic() {
|
||||
return this.props.type === VectorStyle.STYLE_TYPE.STATIC ||
|
||||
!this.props.options.field || !this.props.options.field.name;
|
||||
_excludeFromHeader() {
|
||||
return !this.props.style.isDynamic() || !this.props.style.isComplete() || !this.props.style.getField().getName();
|
||||
}
|
||||
|
||||
_formatValue = value => {
|
||||
|
@ -134,26 +81,19 @@ export class StylePropertyLegendRow extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { name, options, range } = this.props;
|
||||
if (this._isStatic()) {
|
||||
|
||||
const { range, style } = this.props;
|
||||
if (this._excludeFromHeader()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let header;
|
||||
if (options.color) {
|
||||
header = <ColorGradient colorRampName={options.color}/>;
|
||||
} else if (name === 'lineWidth') {
|
||||
header = renderHeaderWithIcons(getLineWidthIcons());
|
||||
} else if (name === 'iconSize') {
|
||||
header = renderHeaderWithIcons(getSymbolSizeIcons());
|
||||
}
|
||||
|
||||
const header = style.renderHeader();
|
||||
return (
|
||||
<StyleLegendRow
|
||||
header={header}
|
||||
minLabel={this._formatValue(_.get(range, 'min', EMPTY_VALUE))}
|
||||
maxLabel={this._formatValue(_.get(range, 'max', EMPTY_VALUE))}
|
||||
propertyLabel={getVectorStyleLabel(name)}
|
||||
propertyLabel={getVectorStyleLabel(style.getStyleName())}
|
||||
fieldLabel={this.state.label}
|
||||
/>
|
||||
);
|
||||
|
@ -161,10 +101,6 @@ export class StylePropertyLegendRow extends Component {
|
|||
}
|
||||
|
||||
StylePropertyLegendRow.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string,
|
||||
options: PropTypes.oneOfType(styleOptionShapes).isRequired,
|
||||
range: rangeShape,
|
||||
getFieldLabel: PropTypes.func.isRequired,
|
||||
getFieldFormatter: PropTypes.func.isRequired,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
|
|
@ -7,34 +7,26 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styleOptionShapes, rangeShape } from '../style_option_shapes';
|
||||
import { rangeShape } from '../style_option_shapes';
|
||||
import { StylePropertyLegendRow } from './style_property_legend_row';
|
||||
|
||||
export function VectorStyleLegend({ getFieldLabel, getFieldFormatter, styleProperties }) {
|
||||
export function VectorStyleLegend({ styleProperties }) {
|
||||
return styleProperties.map(styleProperty => {
|
||||
return (
|
||||
<StylePropertyLegendRow
|
||||
key={styleProperty.name}
|
||||
name={styleProperty.name}
|
||||
type={styleProperty.type}
|
||||
options={styleProperty.options}
|
||||
style={styleProperty.style}
|
||||
key={styleProperty.style.getStyleName()}
|
||||
range={styleProperty.range}
|
||||
getFieldLabel={getFieldLabel}
|
||||
getFieldFormatter={getFieldFormatter}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const stylePropertyShape = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string,
|
||||
options: PropTypes.oneOfType(styleOptionShapes).isRequired,
|
||||
range: rangeShape,
|
||||
style: PropTypes.object
|
||||
});
|
||||
|
||||
VectorStyleLegend.propTypes = {
|
||||
styleProperties: PropTypes.arrayOf(stylePropertyShape).isRequired,
|
||||
getFieldLabel: PropTypes.func.isRequired,
|
||||
getFieldFormatter: PropTypes.func.isRequired,
|
||||
styleProperties: PropTypes.arrayOf(stylePropertyShape).isRequired
|
||||
};
|
||||
|
|
|
@ -36,15 +36,6 @@ export const dynamicSizeShape = PropTypes.shape({
|
|||
field: fieldShape,
|
||||
});
|
||||
|
||||
export const styleOptionShapes = [
|
||||
staticColorShape,
|
||||
dynamicColorShape,
|
||||
staticOrientationShape,
|
||||
dynamicOrientationShape,
|
||||
staticSizeShape,
|
||||
dynamicSizeShape
|
||||
];
|
||||
|
||||
export const rangeShape = PropTypes.shape({
|
||||
min: PropTypes.number.isRequired,
|
||||
max: PropTypes.number.isRequired,
|
||||
|
|
|
@ -52,20 +52,27 @@ export class VectorStyleEditor extends Component {
|
|||
|
||||
async _loadOrdinalFields() {
|
||||
|
||||
const getFieldMeta = async (field) => {
|
||||
return {
|
||||
label: await field.getLabel(),
|
||||
name: field.getName(),
|
||||
origin: field.getOrigin()
|
||||
};
|
||||
};
|
||||
const dateFields = await this.props.layer.getDateFields();
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
if (!_.isEqual(dateFields, this.state.dateFields)) {
|
||||
this.setState({ dateFields });
|
||||
const dateFieldPromises = dateFields.map(getFieldMeta);
|
||||
const dateFieldsArray = await Promise.all(dateFieldPromises);
|
||||
|
||||
if (this._isMounted && !_.isEqual(dateFieldsArray, this.state.dateFields)) {
|
||||
this.setState({ dateFields: dateFieldsArray });
|
||||
}
|
||||
|
||||
const numberFields = await this.props.layer.getNumberFields();
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
if (!_.isEqual(numberFields, this.state.numberFields)) {
|
||||
this.setState({ numberFields });
|
||||
const numberFieldPromises = numberFields.map(getFieldMeta);
|
||||
|
||||
const numberFieldsArray = await Promise.all(numberFieldPromises);
|
||||
if (this._isMounted && !_.isEqual(numberFieldsArray, this.state.numberFields)) {
|
||||
this.setState({ numberFields: numberFieldsArray });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@ import { DynamicStyleProperty } from './dynamic_style_property';
|
|||
import _ from 'lodash';
|
||||
import { getComputedFieldName } from '../style_util';
|
||||
import { getColorRampStops } from '../../color_utils';
|
||||
import { ColorGradient } from '../../components/color_gradient';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
export class DynamicColorProperty extends DynamicStyleProperty {
|
||||
|
||||
|
||||
syncCircleColorWithMb(mbLayerId, mbMap, alpha) {
|
||||
const color = this._getMbColor();
|
||||
mbMap.setPaintProperty(mbLayerId, 'circle-color', color);
|
||||
|
@ -48,6 +49,18 @@ export class DynamicColorProperty extends DynamicStyleProperty {
|
|||
mbMap.setPaintProperty(mbLayerId, 'line-opacity', alpha);
|
||||
}
|
||||
|
||||
isCustomColorRamp() {
|
||||
return !!this._options.customColorRamp;
|
||||
}
|
||||
|
||||
supportsFeatureState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isScaled() {
|
||||
return !this.isCustomColorRamp();
|
||||
}
|
||||
|
||||
_getMbColor() {
|
||||
const isDynamicConfigComplete = _.has(this._options, 'field.name') && _.has(this._options, 'color');
|
||||
if (!isDynamicConfigComplete) {
|
||||
|
@ -98,6 +111,14 @@ export class DynamicColorProperty extends DynamicStyleProperty {
|
|||
return getColorRampStops(this._options.color);
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
if (this._options.color) {
|
||||
return (<ColorGradient colorRampName={this._options.color}/>);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -22,6 +22,14 @@ export class DynamicOrientationProperty extends DynamicStyleProperty {
|
|||
}
|
||||
}
|
||||
|
||||
supportsFeatureState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isScaled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,34 @@ import { getComputedFieldName } from '../style_util';
|
|||
import { HALF_LARGE_MAKI_ICON_SIZE, LARGE_MAKI_ICON_SIZE, SMALL_MAKI_ICON_SIZE } from '../symbol_utils';
|
||||
import { vectorStyles } from '../vector_style_defaults';
|
||||
import _ from 'lodash';
|
||||
import { CircleIcon } from '../components/legend/circle_icon';
|
||||
import React, { Fragment } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
|
||||
|
||||
function getLineWidthIcons() {
|
||||
const defaultStyle = {
|
||||
stroke: 'grey',
|
||||
fill: 'none',
|
||||
width: '12px',
|
||||
};
|
||||
return [
|
||||
<CircleIcon style={{ ...defaultStyle, strokeWidth: '1px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, strokeWidth: '2px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, strokeWidth: '3px' }}/>,
|
||||
];
|
||||
}
|
||||
|
||||
function getSymbolSizeIcons() {
|
||||
const defaultStyle = {
|
||||
stroke: 'grey',
|
||||
fill: 'grey',
|
||||
};
|
||||
return [
|
||||
<CircleIcon style={{ ...defaultStyle, width: '4px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, width: '8px' }}/>,
|
||||
<CircleIcon style={{ ...defaultStyle, width: '12px' }}/>,
|
||||
];
|
||||
}
|
||||
|
||||
export class DynamicSizeProperty extends DynamicStyleProperty {
|
||||
|
||||
|
@ -79,6 +107,43 @@ export class DynamicSizeProperty extends DynamicStyleProperty {
|
|||
}
|
||||
|
||||
_isSizeDynamicConfigComplete() {
|
||||
return this._options.field && this._options.field.name && _.has(this._options, 'minSize') && _.has(this._options, 'maxSize');
|
||||
return this._field && this._field.isValid() && _.has(this._options, 'minSize') && _.has(this._options, 'maxSize');
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
let icons;
|
||||
if (this.getStyleName() === vectorStyles.LINE_WIDTH) {
|
||||
icons = getLineWidthIcons();
|
||||
} else if (this.getStyleName() === vectorStyles.ICON_SIZE) {
|
||||
icons = getSymbolSizeIcons();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween" alignItems="center">
|
||||
{
|
||||
icons.map((icon, index) => {
|
||||
const isLast = index === icons.length - 1;
|
||||
let spacer;
|
||||
if (!isLast) {
|
||||
spacer = (
|
||||
<EuiFlexItem>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{icon}
|
||||
</EuiFlexItem>
|
||||
{spacer}
|
||||
</Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,37 @@
|
|||
|
||||
|
||||
import { AbstractStyleProperty } from './style_property';
|
||||
import { STYLE_TYPE } from '../../../../../common/constants';
|
||||
|
||||
export class DynamicStyleProperty extends AbstractStyleProperty {
|
||||
static type = 'DYNAMIC';
|
||||
static type = STYLE_TYPE.DYNAMIC;
|
||||
|
||||
constructor(options, styleName, field) {
|
||||
super(options, styleName);
|
||||
this._field = field;
|
||||
}
|
||||
|
||||
getField() {
|
||||
return this._field;
|
||||
}
|
||||
|
||||
isDynamic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isComplete() {
|
||||
return !!this._field;
|
||||
}
|
||||
|
||||
getFieldOrigin() {
|
||||
return this._field.getOrigin();
|
||||
}
|
||||
|
||||
supportsFeatureState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isScaled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
|
||||
import { AbstractStyleProperty } from './style_property';
|
||||
import { STYLE_TYPE } from '../../../../../common/constants';
|
||||
|
||||
export class StaticStyleProperty extends AbstractStyleProperty {
|
||||
static type = 'STATIC';
|
||||
|
||||
static type = STYLE_TYPE.STATIC;
|
||||
}
|
||||
|
|
|
@ -10,4 +10,30 @@ export class AbstractStyleProperty {
|
|||
this._options = options;
|
||||
this._styleName = styleName;
|
||||
}
|
||||
|
||||
isDynamic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the style fully defined and usable? (e.g. for rendering, in legend UX, ...)
|
||||
* Why? during editing, partially-completed descriptors may be added to the layer-descriptor
|
||||
* e.g. dynamic-fields can have an incomplete state when the field is not yet selected from the drop-down
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isComplete() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getStyleName() {
|
||||
return this._styleName;
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return this._options || {};
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,17 +6,16 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VectorStyleEditor } from './components/vector_style_editor';
|
||||
import { getDefaultProperties, vectorStyles } from './vector_style_defaults';
|
||||
import { AbstractStyle } from '../abstract_style';
|
||||
import { SOURCE_DATA_ID_ORIGIN, GEO_JSON_TYPE } from '../../../../common/constants';
|
||||
import { GEO_JSON_TYPE, FIELD_ORIGIN, STYLE_TYPE } from '../../../../common/constants';
|
||||
import { VectorIcon } from './components/legend/vector_icon';
|
||||
import { VectorStyleLegend } from './components/legend/vector_style_legend';
|
||||
import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types';
|
||||
import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from './vector_constants';
|
||||
import { getMakiSymbolAnchor } from './symbol_utils';
|
||||
import { getComputedFieldName, getComputedFieldNamePrefix } from './style_util';
|
||||
import { getComputedFieldName } from './style_util';
|
||||
import { StaticStyleProperty } from './properties/static_style_property';
|
||||
import { DynamicStyleProperty } from './properties/dynamic_style_property';
|
||||
import { DynamicSizeProperty } from './properties/dynamic_size_property';
|
||||
|
@ -33,27 +32,7 @@ const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON];
|
|||
export class VectorStyle extends AbstractStyle {
|
||||
|
||||
static type = 'VECTOR';
|
||||
static STYLE_TYPE = { 'DYNAMIC': DynamicStyleProperty.type, 'STATIC': StaticStyleProperty.type };
|
||||
|
||||
static getComputedFieldName = getComputedFieldName;
|
||||
static getComputedFieldNamePrefix = getComputedFieldNamePrefix;
|
||||
|
||||
constructor(descriptor = {}, source) {
|
||||
super();
|
||||
this._source = source;
|
||||
this._descriptor = {
|
||||
...descriptor,
|
||||
...VectorStyle.createDescriptor(descriptor.properties),
|
||||
};
|
||||
|
||||
this._lineColorStyleProperty = this._makeColorProperty(this._descriptor.properties[vectorStyles.LINE_COLOR], vectorStyles.LINE_COLOR);
|
||||
this._fillColorStyleProperty = this._makeColorProperty(this._descriptor.properties[vectorStyles.FILL_COLOR], vectorStyles.FILL_COLOR);
|
||||
this._lineWidthStyleProperty = this._makeSizeProperty(this._descriptor.properties[vectorStyles.LINE_WIDTH], vectorStyles.LINE_WIDTH);
|
||||
this._iconSizeStyleProperty = this._makeSizeProperty(this._descriptor.properties[vectorStyles.ICON_SIZE], vectorStyles.ICON_SIZE);
|
||||
// eslint-disable-next-line max-len
|
||||
this._iconOrientationProperty = this._makeOrientationProperty(this._descriptor.properties[vectorStyles.ICON_ORIENTATION], vectorStyles.ICON_ORIENTATION);
|
||||
}
|
||||
|
||||
static STYLE_TYPE = STYLE_TYPE;
|
||||
static createDescriptor(properties = {}) {
|
||||
return {
|
||||
type: VectorStyle.type,
|
||||
|
@ -65,16 +44,36 @@ export class VectorStyle extends AbstractStyle {
|
|||
return getDefaultProperties(mapColors);
|
||||
}
|
||||
|
||||
static getDisplayName() {
|
||||
return i18n.translate('xpack.maps.style.vector.displayNameLabel', {
|
||||
defaultMessage: 'Vector style'
|
||||
});
|
||||
constructor(descriptor = {}, source, layer) {
|
||||
super();
|
||||
this._source = source;
|
||||
this._layer = layer;
|
||||
this._descriptor = {
|
||||
...descriptor,
|
||||
...VectorStyle.createDescriptor(descriptor.properties),
|
||||
};
|
||||
|
||||
this._lineColorStyleProperty = this._makeColorProperty(this._descriptor.properties[vectorStyles.LINE_COLOR], vectorStyles.LINE_COLOR);
|
||||
this._fillColorStyleProperty = this._makeColorProperty(this._descriptor.properties[vectorStyles.FILL_COLOR], vectorStyles.FILL_COLOR);
|
||||
this._lineWidthStyleProperty = this._makeSizeProperty(this._descriptor.properties[vectorStyles.LINE_WIDTH], vectorStyles.LINE_WIDTH);
|
||||
this._iconSizeStyleProperty = this._makeSizeProperty(this._descriptor.properties[vectorStyles.ICON_SIZE], vectorStyles.ICON_SIZE);
|
||||
// eslint-disable-next-line max-len
|
||||
this._iconOrientationProperty = this._makeOrientationProperty(this._descriptor.properties[vectorStyles.ICON_ORIENTATION], vectorStyles.ICON_ORIENTATION);
|
||||
|
||||
}
|
||||
|
||||
static description = '';
|
||||
_getAllStyleProperties() {
|
||||
return [
|
||||
this._lineColorStyleProperty,
|
||||
this._fillColorStyleProperty,
|
||||
this._lineWidthStyleProperty,
|
||||
this._iconSizeStyleProperty,
|
||||
this._iconOrientationProperty
|
||||
];
|
||||
}
|
||||
|
||||
renderEditor({ layer, onStyleDescriptorChange }) {
|
||||
const styleProperties = { ...this.getProperties() };
|
||||
const styleProperties = { ...this.getRawProperties() };
|
||||
const handlePropertyChange = (propertyName, settings) => {
|
||||
styleProperties[propertyName] = settings;//override single property, but preserve the rest
|
||||
const vectorStyleDescriptor = VectorStyle.createDescriptor(styleProperties);
|
||||
|
@ -104,39 +103,45 @@ export class VectorStyle extends AbstractStyle {
|
|||
* can then use to update store state via dispatch.
|
||||
*/
|
||||
getDescriptorWithMissingStylePropsRemoved(nextOrdinalFields) {
|
||||
const originalProperties = this.getProperties();
|
||||
const updatedProperties = {};
|
||||
Object.keys(originalProperties).forEach(propertyName => {
|
||||
if (!this._isPropertyDynamic(propertyName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldName = _.get(originalProperties[propertyName], 'options.field.name');
|
||||
const originalProperties = this.getRawProperties();
|
||||
const updatedProperties = {};
|
||||
|
||||
const dynamicProperties = Object.keys(originalProperties).filter(key => {
|
||||
const { type, options } = originalProperties[key] || {};
|
||||
return type === STYLE_TYPE.DYNAMIC && options.field && options.field.name;
|
||||
});
|
||||
|
||||
dynamicProperties.forEach(key => {
|
||||
|
||||
const dynamicProperty = originalProperties[key];
|
||||
const fieldName = dynamicProperty && dynamicProperty.options.field && dynamicProperty.options.field.name;
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingOrdinalField = nextOrdinalFields.find(oridinalField => {
|
||||
return fieldName === oridinalField.name;
|
||||
const matchingOrdinalField = nextOrdinalFields.find(ordinalField => {
|
||||
return fieldName === ordinalField.getName();
|
||||
});
|
||||
|
||||
if (matchingOrdinalField) {
|
||||
return;
|
||||
}
|
||||
|
||||
updatedProperties[propertyName] = {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
updatedProperties[key] = {
|
||||
type: DynamicStyleProperty.type,
|
||||
options: {
|
||||
...originalProperties[propertyName].options
|
||||
...originalProperties[key].options
|
||||
}
|
||||
};
|
||||
delete updatedProperties[propertyName].options.field;
|
||||
delete updatedProperties[key].options.field;
|
||||
|
||||
});
|
||||
|
||||
if (Object.keys(updatedProperties).length === 0) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
nextStyleDescriptor: { ...this._descriptor },
|
||||
nextStyleDescriptor: { ...this._descriptor }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -156,9 +161,9 @@ export class VectorStyle extends AbstractStyle {
|
|||
}
|
||||
|
||||
const scaledFields = this.getDynamicPropertiesArray()
|
||||
.map(({ options }) => {
|
||||
.map(styleProperty => {
|
||||
return {
|
||||
name: options.field.name,
|
||||
name: styleProperty.getField().getName(),
|
||||
min: Infinity,
|
||||
max: -Infinity
|
||||
};
|
||||
|
@ -219,45 +224,22 @@ export class VectorStyle extends AbstractStyle {
|
|||
}
|
||||
|
||||
getSourceFieldNames() {
|
||||
const properties = this.getProperties();
|
||||
const fieldNames = [];
|
||||
Object.keys(properties).forEach(propertyName => {
|
||||
if (!this._isPropertyDynamic(propertyName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = _.get(properties[propertyName], 'options.field', {});
|
||||
if (field.origin === SOURCE_DATA_ID_ORIGIN && field.name) {
|
||||
fieldNames.push(field.name);
|
||||
this.getDynamicPropertiesArray().forEach(styleProperty => {
|
||||
if (styleProperty.getFieldOrigin() === FIELD_ORIGIN.SOURCE) {
|
||||
fieldNames.push(styleProperty.getField().getName());
|
||||
}
|
||||
});
|
||||
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
getProperties() {
|
||||
getRawProperties() {
|
||||
return this._descriptor.properties || {};
|
||||
}
|
||||
|
||||
getDynamicPropertiesArray() {
|
||||
const styles = this.getProperties();
|
||||
return Object.keys(styles)
|
||||
.map(styleName => {
|
||||
const { type, options } = styles[styleName];
|
||||
return {
|
||||
styleName,
|
||||
type,
|
||||
options
|
||||
};
|
||||
})
|
||||
.filter(({ styleName }) => {
|
||||
return this._isPropertyDynamic(styleName);
|
||||
});
|
||||
}
|
||||
|
||||
_isPropertyDynamic(propertyName) {
|
||||
const { type, options } = _.get(this._descriptor, ['properties', propertyName], {});
|
||||
return type === VectorStyle.STYLE_TYPE.DYNAMIC && options.field && options.field.name;
|
||||
const styleProperties = this._getAllStyleProperties();
|
||||
return styleProperties.filter(styleProperty => (styleProperty.isDynamic() && styleProperty.isComplete()));
|
||||
}
|
||||
|
||||
_checkIfOnlyFeatureType = async (featureType) => {
|
||||
|
@ -288,16 +270,12 @@ export class VectorStyle extends AbstractStyle {
|
|||
return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.LINE);
|
||||
}
|
||||
|
||||
_getIsPolygonsOnly = async () => {
|
||||
return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.POLYGON);
|
||||
}
|
||||
|
||||
_getFieldRange = (fieldName) => {
|
||||
return _.get(this._descriptor, ['__styleMeta', fieldName]);
|
||||
}
|
||||
|
||||
getIcon = () => {
|
||||
const styles = this.getProperties();
|
||||
const styles = this.getRawProperties();
|
||||
const symbolId = this.arePointsSymbolizedAsCircles()
|
||||
? undefined
|
||||
: this._descriptor.properties.symbol.options.symbolId;
|
||||
|
@ -305,65 +283,54 @@ export class VectorStyle extends AbstractStyle {
|
|||
<VectorIcon
|
||||
loadIsPointsOnly={this._getIsPointsOnly}
|
||||
loadIsLinesOnly={this._getIsLinesOnly}
|
||||
fillColor={styles.fillColor}
|
||||
lineColor={styles.lineColor}
|
||||
fillColor={styles[vectorStyles.FILL_COLOR]}
|
||||
lineColor={styles[vectorStyles.LINE_COLOR]}
|
||||
symbolId={symbolId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getLegendDetails(getFieldLabel, getFieldFormatter) {
|
||||
const styles = this.getProperties();
|
||||
const styleProperties = Object.keys(styles).map(styleName => {
|
||||
const { type, options } = styles[styleName];
|
||||
renderLegendDetails() {
|
||||
const styles = this._getAllStyleProperties();
|
||||
const styleProperties = styles.map((style) => {
|
||||
return {
|
||||
name: styleName,
|
||||
type,
|
||||
options,
|
||||
range: options && options.field && options.field.name ? this._getFieldRange(options.field.name) : null,
|
||||
// eslint-disable-next-line max-len
|
||||
range: (style.isDynamic() && style.isComplete() && style.getField().getName()) ? this._getFieldRange(style.getField().getName()) : null,
|
||||
style: style
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<VectorStyleLegend
|
||||
styleProperties={styleProperties}
|
||||
getFieldLabel={getFieldLabel}
|
||||
getFieldFormatter={getFieldFormatter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_getStyleFields() {
|
||||
return this.getDynamicPropertiesArray()
|
||||
.map(({ styleName, options }) => {
|
||||
const name = options.field.name;
|
||||
.map(styleProperty => {
|
||||
|
||||
// "feature-state" data expressions are not supported with layout properties.
|
||||
// To work around this limitation, some styling values must fall back to geojson property values.
|
||||
let supportsFeatureState;
|
||||
let isScaled;
|
||||
if (styleName === 'iconSize'
|
||||
if (styleProperty.getStyleName() === vectorStyles.ICON_SIZE
|
||||
&& this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_ICON) {
|
||||
supportsFeatureState = false;
|
||||
isScaled = true;
|
||||
} else if (styleName === 'iconOrientation') {
|
||||
supportsFeatureState = false;
|
||||
isScaled = false;
|
||||
} else if ((styleName === vectorStyles.FILL_COLOR || styleName === vectorStyles.LINE_COLOR)
|
||||
&& options.useCustomColorRamp) {
|
||||
supportsFeatureState = true;
|
||||
isScaled = false;
|
||||
} else {
|
||||
supportsFeatureState = true;
|
||||
isScaled = true;
|
||||
supportsFeatureState = styleProperty.supportsFeatureState();
|
||||
isScaled = styleProperty.isScaled();
|
||||
}
|
||||
|
||||
const field = styleProperty.getField();
|
||||
return {
|
||||
supportsFeatureState,
|
||||
isScaled,
|
||||
name,
|
||||
range: this._getFieldRange(name),
|
||||
computedName: VectorStyle.getComputedFieldName(styleName, name),
|
||||
name: field.getName(),
|
||||
range: this._getFieldRange(field.getName()),
|
||||
computedName: getComputedFieldName(styleProperty.getStyleName(), field.getName()),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -472,13 +439,46 @@ export class VectorStyle extends AbstractStyle {
|
|||
|
||||
}
|
||||
|
||||
arePointsSymbolizedAsCircles() {
|
||||
return this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_CIRCLE;
|
||||
}
|
||||
|
||||
_makeField(fieldDescriptor) {
|
||||
|
||||
if (!fieldDescriptor || !fieldDescriptor.name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//fieldDescriptor.label is ignored. This is essentially cruft duplicating label-info from the metric-selection
|
||||
//Ignore this custom label
|
||||
if (fieldDescriptor.origin === FIELD_ORIGIN.SOURCE) {
|
||||
return this._source.createField({
|
||||
fieldName: fieldDescriptor.name
|
||||
});
|
||||
} else if (fieldDescriptor.origin === FIELD_ORIGIN.JOIN) {
|
||||
let matchingField = null;
|
||||
const joins = this._layer.getValidJoins();
|
||||
joins.find(join => {
|
||||
const aggSource = join.getRightJoinSource();
|
||||
matchingField = aggSource.getMetricFieldForName(fieldDescriptor.name);
|
||||
return !!matchingField;
|
||||
});
|
||||
return matchingField;
|
||||
} else {
|
||||
throw new Error(`Unknown origin-type ${fieldDescriptor.origin}`);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
_makeSizeProperty(descriptor, styleName) {
|
||||
if (!descriptor || !descriptor.options) {
|
||||
return new StaticSizeProperty({ size: 0 }, styleName);
|
||||
} else if (descriptor.type === StaticStyleProperty.type) {
|
||||
return new StaticSizeProperty(descriptor.options, styleName);
|
||||
} else if (descriptor.type === DynamicStyleProperty.type) {
|
||||
return new DynamicSizeProperty(descriptor.options, styleName);
|
||||
const field = this._makeField(descriptor.options.field);
|
||||
return new DynamicSizeProperty(descriptor.options, styleName, field);
|
||||
} else {
|
||||
throw new Error(`${descriptor} not implemented`);
|
||||
}
|
||||
|
@ -490,7 +490,8 @@ export class VectorStyle extends AbstractStyle {
|
|||
} else if (descriptor.type === StaticStyleProperty.type) {
|
||||
return new StaticColorProperty(descriptor.options, styleName);
|
||||
} else if (descriptor.type === DynamicStyleProperty.type) {
|
||||
return new DynamicColorProperty(descriptor.options, styleName);
|
||||
const field = this._makeField(descriptor.options.field);
|
||||
return new DynamicColorProperty(descriptor.options, styleName, field);
|
||||
} else {
|
||||
throw new Error(`${descriptor} not implemented`);
|
||||
}
|
||||
|
@ -502,7 +503,8 @@ export class VectorStyle extends AbstractStyle {
|
|||
} else if (descriptor.type === StaticStyleProperty.type) {
|
||||
return new StaticOrientationProperty(descriptor.options, styleName);
|
||||
} else if (descriptor.type === DynamicStyleProperty.type) {
|
||||
return new DynamicOrientationProperty(descriptor.options, styleName);
|
||||
const field = this._makeField(descriptor.options.field);
|
||||
return new DynamicOrientationProperty(descriptor.options, styleName, field);
|
||||
} else {
|
||||
throw new Error(`${descriptor} not implemented`);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,35 @@
|
|||
import { VectorStyle } from './vector_style';
|
||||
import { DataRequest } from '../../util/data_request';
|
||||
import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types';
|
||||
import { FIELD_ORIGIN } from '../../../../common/constants';
|
||||
|
||||
class MockField {
|
||||
constructor({ fieldName }) {
|
||||
this._fieldName = fieldName;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this._fieldName;
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this._fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
class MockSource {
|
||||
|
||||
constructor({ supportedShapeTypes } = {}) {
|
||||
this._supportedShapeTypes = supportedShapeTypes || Object.values(VECTOR_SHAPE_TYPES);
|
||||
}
|
||||
getSupportedShapeTypes() {
|
||||
return this._supportedShapeTypes;
|
||||
}
|
||||
createField({ fieldName }) {
|
||||
return new MockField({ fieldName });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe('getDescriptorWithMissingStylePropsRemoved', () => {
|
||||
const fieldName = 'doIStillExist';
|
||||
|
@ -17,29 +46,32 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
|
|||
},
|
||||
lineColor: {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
options: {}
|
||||
options: {
|
||||
'field': {
|
||||
'name': fieldName,
|
||||
'origin': FIELD_ORIGIN.SOURCE
|
||||
}
|
||||
}
|
||||
},
|
||||
iconSize: {
|
||||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
options: {
|
||||
color: 'a color',
|
||||
field: { name: fieldName }
|
||||
field: { name: fieldName, origin: FIELD_ORIGIN.SOURCE }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
it('Should return no changes when next oridinal fields contain existing style property fields', () => {
|
||||
const vectorStyle = new VectorStyle({ properties });
|
||||
const vectorStyle = new VectorStyle({ properties }, new MockSource());
|
||||
|
||||
const nextOridinalFields = [
|
||||
{ name: fieldName }
|
||||
];
|
||||
const nextOridinalFields = [new MockField({ fieldName })];
|
||||
const { hasChanges } = vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextOridinalFields);
|
||||
expect(hasChanges).toBe(false);
|
||||
});
|
||||
|
||||
it('Should clear missing fields when next oridinal fields do not contain existing style property fields', () => {
|
||||
const vectorStyle = new VectorStyle({ properties });
|
||||
const vectorStyle = new VectorStyle({ properties }, new MockSource());
|
||||
|
||||
const nextOridinalFields = [];
|
||||
const { hasChanges, nextStyleDescriptor } = vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextOridinalFields);
|
||||
|
@ -83,12 +115,6 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
|
|||
|
||||
describe('pluckStyleMetaFromSourceDataRequest', () => {
|
||||
|
||||
const sourceMock = {
|
||||
getSupportedShapeTypes: () => {
|
||||
return Object.values(VECTOR_SHAPE_TYPES);
|
||||
}
|
||||
};
|
||||
|
||||
describe('has features', () => {
|
||||
it('Should identify when feature collection only contains points', async () => {
|
||||
const sourceDataRequest = new DataRequest({
|
||||
|
@ -110,7 +136,7 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
|
|||
],
|
||||
}
|
||||
});
|
||||
const vectorStyle = new VectorStyle({}, sourceMock);
|
||||
const vectorStyle = new VectorStyle({}, new MockSource());
|
||||
|
||||
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
|
||||
expect(featuresMeta.hasFeatureType).toEqual({
|
||||
|
@ -140,7 +166,7 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
|
|||
],
|
||||
}
|
||||
});
|
||||
const vectorStyle = new VectorStyle({}, sourceMock);
|
||||
const vectorStyle = new VectorStyle({}, new MockSource());
|
||||
|
||||
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
|
||||
expect(featuresMeta.hasFeatureType).toEqual({
|
||||
|
@ -183,12 +209,13 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
|
|||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
options: {
|
||||
field: {
|
||||
origin: FIELD_ORIGIN.SOURCE,
|
||||
name: 'myDynamicFieldWithNoValues'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, sourceMock);
|
||||
}, new MockSource());
|
||||
|
||||
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
|
||||
expect(featuresMeta.hasFeatureType).toEqual({
|
||||
|
@ -205,12 +232,13 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
|
|||
type: VectorStyle.STYLE_TYPE.DYNAMIC,
|
||||
options: {
|
||||
field: {
|
||||
origin: FIELD_ORIGIN.SOURCE,
|
||||
name: 'myDynamicField'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, sourceMock);
|
||||
}, new MockSource());
|
||||
|
||||
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
|
||||
expect(featuresMeta.myDynamicField).toEqual({
|
||||
|
@ -226,32 +254,24 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
|
|||
describe('checkIfOnlyFeatureType', () => {
|
||||
|
||||
describe('source supports single feature type', () => {
|
||||
const sourceMock = {
|
||||
getSupportedShapeTypes: () => {
|
||||
return [VECTOR_SHAPE_TYPES.POINT];
|
||||
}
|
||||
};
|
||||
|
||||
it('isPointsOnly should be true when source feature type only supports points', async () => {
|
||||
const vectorStyle = new VectorStyle({}, sourceMock);
|
||||
const vectorStyle = new VectorStyle({}, new MockSource({
|
||||
supportedShapeTypes: [VECTOR_SHAPE_TYPES.POINT]
|
||||
}));
|
||||
const isPointsOnly = await vectorStyle._getIsPointsOnly();
|
||||
expect(isPointsOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('isLineOnly should be false when source feature type only supports points', async () => {
|
||||
const vectorStyle = new VectorStyle({}, sourceMock);
|
||||
const vectorStyle = new VectorStyle({}, new MockSource({
|
||||
supportedShapeTypes: [VECTOR_SHAPE_TYPES.POINT]
|
||||
}));
|
||||
const isLineOnly = await vectorStyle._getIsLinesOnly();
|
||||
expect(isLineOnly).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('source supports multiple feature types', () => {
|
||||
const sourceMock = {
|
||||
getSupportedShapeTypes: () => {
|
||||
return Object.values(VECTOR_SHAPE_TYPES);
|
||||
}
|
||||
};
|
||||
|
||||
it('isPointsOnly should be true when data contains just points', async () => {
|
||||
const vectorStyle = new VectorStyle({
|
||||
__styleMeta: {
|
||||
|
@ -261,7 +281,9 @@ describe('checkIfOnlyFeatureType', () => {
|
|||
POLYGON: false
|
||||
}
|
||||
}
|
||||
}, sourceMock);
|
||||
}, new MockSource({
|
||||
supportedShapeTypes: Object.values(VECTOR_SHAPE_TYPES)
|
||||
}));
|
||||
const isPointsOnly = await vectorStyle._getIsPointsOnly();
|
||||
expect(isPointsOnly).toBe(true);
|
||||
});
|
||||
|
@ -275,7 +297,9 @@ describe('checkIfOnlyFeatureType', () => {
|
|||
POLYGON: false
|
||||
}
|
||||
}
|
||||
}, sourceMock);
|
||||
}, new MockSource({
|
||||
supportedShapeTypes: Object.values(VECTOR_SHAPE_TYPES)
|
||||
}));
|
||||
const isPointsOnly = await vectorStyle._getIsPointsOnly();
|
||||
expect(isPointsOnly).toBe(false);
|
||||
});
|
||||
|
@ -289,7 +313,9 @@ describe('checkIfOnlyFeatureType', () => {
|
|||
POLYGON: true
|
||||
}
|
||||
}
|
||||
}, sourceMock);
|
||||
}, new MockSource({
|
||||
supportedShapeTypes: Object.values(VECTOR_SHAPE_TYPES)
|
||||
}));
|
||||
const isPointsOnly = await vectorStyle._getIsPointsOnly();
|
||||
expect(isPointsOnly).toBe(false);
|
||||
});
|
||||
|
|
|
@ -12,10 +12,6 @@ export class TileLayer extends AbstractLayer {
|
|||
|
||||
static type = LAYER_TYPE.TILE;
|
||||
|
||||
constructor({ layerDescriptor, source, style }) {
|
||||
super({ layerDescriptor, source, style });
|
||||
}
|
||||
|
||||
static createDescriptor(options) {
|
||||
const tileLayerDescriptor = super.createDescriptor(options);
|
||||
tileLayerDescriptor.type = TileLayer.type;
|
||||
|
|
|
@ -14,6 +14,7 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty {
|
|||
super(propertyKey, propertyName, rawValue, indexPattern);
|
||||
this._metricField = metricField;
|
||||
}
|
||||
|
||||
isFilterable() {
|
||||
return false;
|
||||
}
|
||||
|
@ -22,10 +23,10 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty {
|
|||
if (typeof this._rawValue === 'undefined') {
|
||||
return '-';
|
||||
}
|
||||
if (this._metricField.type === METRIC_TYPE.COUNT || this._metricField.type === METRIC_TYPE.UNIQUE_COUNT) {
|
||||
if (this._metricField.getAggType() === METRIC_TYPE.COUNT || this._metricField.getAggType() === METRIC_TYPE.UNIQUE_COUNT) {
|
||||
return this._rawValue;
|
||||
}
|
||||
const indexPatternField = this._indexPattern.fields.getByName(this._metricField.field);
|
||||
const indexPatternField = this._indexPattern.fields.getByName(this._metricField.getESDocFieldName());
|
||||
if (!indexPatternField) {
|
||||
return this._rawValue;
|
||||
}
|
||||
|
|
|
@ -38,12 +38,14 @@ export class JoinTooltipProperty extends TooltipProperty {
|
|||
|
||||
for (let i = 0; i < this._leftInnerJoins.length; i++) {
|
||||
const rightSource = this._leftInnerJoins[i].getRightJoinSource();
|
||||
const esTooltipProperty = await rightSource.createESTooltipProperty(
|
||||
rightSource.getTerm(),
|
||||
this._tooltipProperty.getRawValue()
|
||||
);
|
||||
if (esTooltipProperty) {
|
||||
esFilters.push(...(await esTooltipProperty.getESFilters()));
|
||||
const termField = rightSource.getTermField();
|
||||
try {
|
||||
const esTooltipProperty = await termField.createTooltipProperty(this._tooltipProperty.getRawValue());
|
||||
if (esTooltipProperty) {
|
||||
esFilters.push(...(await esTooltipProperty.getESFilters()));
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot create joined filter', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@ import {
|
|||
SOURCE_DATA_ID_ORIGIN,
|
||||
FEATURE_VISIBLE_PROPERTY_NAME,
|
||||
EMPTY_FEATURE_COLLECTION,
|
||||
LAYER_TYPE,
|
||||
FIELD_ORIGIN,
|
||||
LAYER_TYPE
|
||||
} from '../../common/constants';
|
||||
import _ from 'lodash';
|
||||
import { JoinTooltipProperty } from './tooltips/join_tooltip_property';
|
||||
|
@ -91,9 +90,11 @@ export class VectorLayer extends AbstractLayer {
|
|||
this._joins = [];
|
||||
if (options.layerDescriptor.joins) {
|
||||
options.layerDescriptor.joins.forEach((joinDescriptor) => {
|
||||
this._joins.push(new InnerJoin(joinDescriptor, this._source.getInspectorAdapters()));
|
||||
const join = new InnerJoin(joinDescriptor, this._source);
|
||||
this._joins.push(join);
|
||||
});
|
||||
}
|
||||
this._style = new VectorStyle(this._descriptor.style, this._source, this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -181,26 +182,8 @@ export class VectorLayer extends AbstractLayer {
|
|||
return this._style.getDynamicPropertiesArray().length > 0;
|
||||
}
|
||||
|
||||
getLegendDetails() {
|
||||
const getFieldLabel = async fieldName => {
|
||||
const ordinalFields = await this._getOrdinalFields();
|
||||
const field = ordinalFields.find(({ name }) => {
|
||||
return name === fieldName;
|
||||
});
|
||||
|
||||
return field ? field.label : fieldName;
|
||||
};
|
||||
|
||||
const getFieldFormatter = async field => {
|
||||
const source = this._getFieldSource(field);
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await source.getFieldFormatter(field.name);
|
||||
};
|
||||
|
||||
return this._style.getLegendDetails(getFieldLabel, getFieldFormatter);
|
||||
renderLegendDetails() {
|
||||
return this._style.renderLegendDetails();
|
||||
}
|
||||
|
||||
_getBoundsBasedOnData() {
|
||||
|
@ -241,46 +224,24 @@ export class VectorLayer extends AbstractLayer {
|
|||
return this._source.getDisplayName();
|
||||
}
|
||||
|
||||
|
||||
async getDateFields() {
|
||||
const timeFields = await this._source.getDateFields();
|
||||
return timeFields.map(({ label, name }) => {
|
||||
return {
|
||||
label,
|
||||
name,
|
||||
origin: SOURCE_DATA_ID_ORIGIN
|
||||
};
|
||||
});
|
||||
return await this._source.getDateFields();
|
||||
}
|
||||
|
||||
|
||||
async getNumberFields() {
|
||||
const numberFields = await this._source.getNumberFields();
|
||||
const numberFieldOptions = numberFields.map(({ label, name }) => {
|
||||
return {
|
||||
label,
|
||||
name,
|
||||
origin: FIELD_ORIGIN.SOURCE
|
||||
};
|
||||
});
|
||||
const numberFieldOptions = await this._source.getNumberFields();
|
||||
const joinFields = [];
|
||||
this.getValidJoins().forEach(join => {
|
||||
const fields = join.getJoinFields().map(joinField => {
|
||||
return {
|
||||
...joinField,
|
||||
origin: FIELD_ORIGIN.JOIN,
|
||||
};
|
||||
});
|
||||
const fields = join.getJoinFields();
|
||||
joinFields.push(...fields);
|
||||
});
|
||||
|
||||
return [...numberFieldOptions, ...joinFields];
|
||||
}
|
||||
|
||||
async _getOrdinalFields() {
|
||||
async getOrdinalFields() {
|
||||
return [
|
||||
... await this.getDateFields(),
|
||||
... await this.getNumberFields()
|
||||
...await this.getDateFields(),
|
||||
...await this.getNumberFields()
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -391,7 +352,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
|
||||
const joinSource = join.getRightJoinSource();
|
||||
const sourceDataId = join.getSourceId();
|
||||
const requestToken = Symbol(`layer-join-refresh:${ this.getId()} - ${sourceDataId}`);
|
||||
const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`);
|
||||
|
||||
const searchFilters = {
|
||||
...dataFilters,
|
||||
|
@ -418,7 +379,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
} = await joinSource.getPropertiesMap(
|
||||
searchFilters,
|
||||
leftSourceName,
|
||||
join.getLeftFieldName(),
|
||||
join.getLeftField().getName(),
|
||||
registerCancelCallback.bind(null, requestToken));
|
||||
stopLoading(sourceDataId, requestToken, propertiesMap);
|
||||
return {
|
||||
|
@ -450,9 +411,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
const fieldNames = [
|
||||
...this._source.getFieldNames(),
|
||||
...this._style.getSourceFieldNames(),
|
||||
...this.getValidJoins().map(join => {
|
||||
return join.getLeftFieldName();
|
||||
})
|
||||
...this.getValidJoins().map(join => join.getLeftField().getName())
|
||||
];
|
||||
|
||||
return {
|
||||
|
@ -484,9 +443,8 @@ export class VectorLayer extends AbstractLayer {
|
|||
let isFeatureVisible = true;
|
||||
for (let j = 0; j < joinStates.length; j++) {
|
||||
const joinState = joinStates[j];
|
||||
const InnerJoin = joinState.join;
|
||||
const rightMetricFields = InnerJoin.getRightMetricFields();
|
||||
const canJoinOnCurrent = InnerJoin.joinPropertiesToFeature(feature, joinState.propertiesMap, rightMetricFields);
|
||||
const innerJoin = joinState.join;
|
||||
const canJoinOnCurrent = innerJoin.joinPropertiesToFeature(feature, joinState.propertiesMap);
|
||||
isFeatureVisible = isFeatureVisible && canJoinOnCurrent;
|
||||
}
|
||||
|
||||
|
@ -506,7 +464,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
startLoading, stopLoading, onLoadError, registerCancelCallback, dataFilters
|
||||
}) {
|
||||
|
||||
const requestToken = Symbol(`layer-source-refresh:${ this.getId()} - source`);
|
||||
const requestToken = Symbol(`layer-source-refresh:${this.getId()} - source`);
|
||||
const searchFilters = this._getSearchFilters(dataFilters);
|
||||
const canSkip = await this._canSkipSourceUpdate(this._source, SOURCE_DATA_ID_ORIGIN, searchFilters);
|
||||
if (canSkip) {
|
||||
|
@ -543,7 +501,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
_assignIdsToFeatures(featureCollection) {
|
||||
|
||||
//wrt https://github.com/elastic/kibana/issues/39317
|
||||
// In constrained resource environments, mapbox-gl may throw a stackoverflow error due to hitting the browser's recursion limit. This crashes Kibana.
|
||||
//In constrained resource environments, mapbox-gl may throw a stackoverflow error due to hitting the browser's recursion limit. This crashes Kibana.
|
||||
//This error is thrown in mapbox-gl's quicksort implementation, when it is sorting all the features by id.
|
||||
//This is a work-around to avoid hitting such a worst-case
|
||||
//This was tested as a suitable work-around for mapbox-gl 0.54
|
||||
|
@ -770,7 +728,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
const tooltipProperty = tooltipsFromSource[i];
|
||||
const matchingJoins = [];
|
||||
for (let j = 0; j < this._joins.length; j++) {
|
||||
if (this._joins[j].getLeftFieldName() === tooltipProperty.getPropertyKey()) {
|
||||
if (this._joins[j].getLeftField().getName() === tooltipProperty.getPropertyKey()) {
|
||||
matchingJoins.push(this._joins[j]);
|
||||
}
|
||||
}
|
||||
|
@ -806,28 +764,4 @@ export class VectorLayer extends AbstractLayer {
|
|||
return feature.properties[FEATURE_ID_PROPERTY_NAME] === id;
|
||||
});
|
||||
}
|
||||
|
||||
_getFieldSource(field) {
|
||||
if (!field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (field.origin === FIELD_ORIGIN.SOURCE) {
|
||||
return this._source;
|
||||
}
|
||||
|
||||
const join = this.getValidJoins().find(join => {
|
||||
const matchingField = join.getJoinFields().find(joinField => {
|
||||
return joinField.name === field.name;
|
||||
});
|
||||
return !!matchingField;
|
||||
});
|
||||
|
||||
if (!join) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return join.getRightJoinSource();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,6 @@ export class VectorTileLayer extends TileLayer {
|
|||
|
||||
static type = LAYER_TYPE.VECTOR_TILE;
|
||||
|
||||
constructor({ layerDescriptor, source, style }) {
|
||||
super({ layerDescriptor, source, style });
|
||||
}
|
||||
|
||||
static createDescriptor(options) {
|
||||
const tileLayerDescriptor = super.createDescriptor(options);
|
||||
tileLayerDescriptor.type = VectorTileLayer.type;
|
||||
|
|
|
@ -11,24 +11,22 @@ import { VectorTileLayer } from '../layers/vector_tile_layer';
|
|||
import { VectorLayer } from '../layers/vector_layer';
|
||||
import { HeatmapLayer } from '../layers/heatmap_layer';
|
||||
import { ALL_SOURCES } from '../layers/sources/all_sources';
|
||||
import { VectorStyle } from '../layers/styles/vector/vector_style';
|
||||
import { HeatmapStyle } from '../layers/styles/heatmap/heatmap_style';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { getInspectorAdapters } from '../reducers/non_serializable_instances';
|
||||
import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../reducers/util';
|
||||
|
||||
function createLayerInstance(layerDescriptor, inspectorAdapters) {
|
||||
const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters);
|
||||
const style = createStyleInstance(layerDescriptor.style, source);
|
||||
|
||||
switch (layerDescriptor.type) {
|
||||
case TileLayer.type:
|
||||
return new TileLayer({ layerDescriptor, source, style });
|
||||
return new TileLayer({ layerDescriptor, source });
|
||||
case VectorLayer.type:
|
||||
return new VectorLayer({ layerDescriptor, source, style });
|
||||
return new VectorLayer({ layerDescriptor, source });
|
||||
case VectorTileLayer.type:
|
||||
return new VectorTileLayer({ layerDescriptor, source, style });
|
||||
return new VectorTileLayer({ layerDescriptor, source });
|
||||
case HeatmapLayer.type:
|
||||
return new HeatmapLayer({ layerDescriptor, source, style });
|
||||
return new HeatmapLayer({ layerDescriptor, source });
|
||||
default:
|
||||
throw new Error(`Unrecognized layerType ${layerDescriptor.type}`);
|
||||
}
|
||||
|
@ -44,25 +42,6 @@ function createSourceInstance(sourceDescriptor, inspectorAdapters) {
|
|||
return new Source(sourceDescriptor, inspectorAdapters);
|
||||
}
|
||||
|
||||
|
||||
function createStyleInstance(styleDescriptor, source) {
|
||||
|
||||
if (!styleDescriptor || !styleDescriptor.type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (styleDescriptor.type) {
|
||||
case 'TILE'://backfill for old tilestyles.
|
||||
return null;
|
||||
case VectorStyle.type:
|
||||
return new VectorStyle(styleDescriptor, source);
|
||||
case HeatmapStyle.type:
|
||||
return new HeatmapStyle(styleDescriptor);
|
||||
default:
|
||||
throw new Error(`Unrecognized styleType ${styleDescriptor.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const getTooltipState = ({ map }) => {
|
||||
return map.tooltipState;
|
||||
};
|
||||
|
|
|
@ -6623,7 +6623,6 @@
|
|||
"xpack.maps.source.wmsTitle": "ウェブマップサービス",
|
||||
"xpack.maps.style.heatmap.displayNameLabel": "ヒートマップスタイル",
|
||||
"xpack.maps.style.heatmap.resolutionStyleErrorMessage": "解像度パラメーターが認識されません: {resolution}",
|
||||
"xpack.maps.style.vector.displayNameLabel": "ベクタースタイル",
|
||||
"xpack.maps.styles.staticDynamic.dynamicDescription": "プロパティ値で特徴をシンボル化します。",
|
||||
"xpack.maps.styles.staticDynamic.staticDescription": "静的スタイルプロパティで特徴をシンボル化します。",
|
||||
"xpack.maps.styles.vector.borderColorLabel": "境界線の色",
|
||||
|
|
|
@ -6564,7 +6564,6 @@
|
|||
"xpack.maps.source.wmsTitle": "Web 地图服务",
|
||||
"xpack.maps.style.heatmap.displayNameLabel": "热图样式",
|
||||
"xpack.maps.style.heatmap.resolutionStyleErrorMessage": "无法识别分辨率参数:{resolution}",
|
||||
"xpack.maps.style.vector.displayNameLabel": "矢量样式",
|
||||
"xpack.maps.styles.staticDynamic.dynamicDescription": "使用属性值代表功能。",
|
||||
"xpack.maps.styles.staticDynamic.staticDescription": "使用静态样式属性代表功能。",
|
||||
"xpack.maps.styles.vector.borderColorLabel": "边框颜色",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue