Skip to content

Commit

Permalink
[Maps] Support categorical styling for numbers and dates (elastic#57908)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasneirynck committed Mar 9, 2020
1 parent 88d21df commit 87c05d9
Show file tree
Hide file tree
Showing 18 changed files with 320 additions and 183 deletions.
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export const COLOR_MAP_TYPE = {
export const COLOR_PALETTE_MAX_SIZE = 10;

export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean'];
export const ORDINAL_DATA_TYPES = ['number', 'date'];

export const SYMBOLIZE_AS_TYPES = {
CIRCLE: 'circle',
Expand Down
8 changes: 4 additions & 4 deletions x-pack/legacy/plugins/maps/common/descriptor_types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ export type AggDescriptor = {
type: AGG_TYPE;
};

export type AbstractESAggDescriptor = AbstractESSourceDescriptor & {
export type AbstractESAggSourceDescriptor = AbstractESSourceDescriptor & {
metrics: AggDescriptor[];
};

export type ESGeoGridSourceDescriptor = AbstractESAggDescriptor & {
export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & {
requestType?: RENDER_AS;
resolution?: GRID_RESOLUTION;
};
Expand All @@ -54,12 +54,12 @@ export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & {
topHitsSize?: number;
};

export type ESPewPewSourceDescriptor = AbstractESAggDescriptor & {
export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
sourceGeoField: string;
destGeoField: string;
};

export type ESTermSourceDescriptor = AbstractESAggDescriptor & {
export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & {
indexPatternTitle: string;
term: string; // term field name
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ export class ESDocField extends AbstractField {

async getCategoricalFieldMetaRequest() {
const field = await this._getField();
if (field.type !== 'string') {
//UX does not support categorical styling for number/date fields
return null;
}

const topTerms = {
size: COLOR_PALETTE_MAX_SIZE - 1, //need additional color for the "other"-value
};
Expand Down
12 changes: 0 additions & 12 deletions x-pack/legacy/plugins/maps/public/layers/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,18 +332,6 @@ export class AbstractLayer {
return [];
}

async getDateFields() {
return [];
}

async getNumberFields() {
return [];
}

async getCategoricalFields() {
return [];
}

async getFields() {
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
import { IESSource } from './es_source';
import { AbstractESSource } from './es_source';
import { AGG_TYPE } from '../../../common/constants';
import { IESAggField } from '../fields/es_agg_field';
import { AbstractESAggSourceDescriptor } from '../../../common/descriptor_types';

export interface IESAggSource extends IESSource {
getAggKey(aggType: AGG_TYPE, fieldName: string): string;
getAggLabel(aggType: AGG_TYPE, fieldName: string): string;
getMetricFields(): IESAggField[];
}

export class AbstractESAggSource extends AbstractESSource implements IESAggSource {
constructor(sourceDescriptor: AbstractESAggSourceDescriptor, inspectorAdapters: object);

getAggKey(aggType: AGG_TYPE, fieldName: string): string;
getAggLabel(aggType: AGG_TYPE, fieldName: string): string;
getMetricFields(): IESAggField[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export class AbstractESAggSource extends AbstractESSource {
}
}

async getFields() {
return this.getMetricFields();
}

getValueAggsDsl(indexPattern) {
const valueAggsDsl = {};
this.getMetricFields().forEach(esAggMetric => {
Expand All @@ -89,10 +93,6 @@ export class AbstractESAggSource extends AbstractESSource {
return valueAggsDsl;
}

async getNumberFields() {
return this.getMetricFields();
}

async filterAndFormatPropertiesToHtmlForMetricFields(properties) {
const metricFields = this.getMetricFields();
const tooltipPropertiesPromises = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { AbstractESAggSource } from './es_agg_source';
import { IField } from '../fields/field';
import { IESAggField } from '../fields/es_agg_field';
import _ from 'lodash';
import { AGG_TYPE } from '../../../common/constants';
import { AggDescriptor } from '../../../common/descriptor_types';

jest.mock('ui/new_platform');

const sumFieldName = 'myFieldGettingSummed';
const metricExamples = [
{
type: AGG_TYPE.SUM,
field: sumFieldName,
label: 'my custom label',
},
{
// metric config is invalid beause field is missing
type: AGG_TYPE.MAX,
},
{
// metric config is valid because "count" metric does not need to provide field
type: AGG_TYPE.COUNT,
label: '', // should ignore empty label fields
},
];

class TestESAggSource extends AbstractESAggSource {
constructor(metrics: AggDescriptor[]) {
super({ type: 'test', id: 'foobar', indexPatternId: 'foobarid', metrics }, []);
}
}

describe('getMetricFields', () => {
it('should add default "count" metric when no metrics are provided', async () => {
const source = new TestESAggSource([]);
const metrics = source.getMetricFields();
expect(metrics.length).toBe(1);

expect(metrics[0].getName()).toEqual('doc_count');
expect(await metrics[0].getLabel()).toEqual('count');
});

it('should remove incomplete metric configurations', async () => {
const source = new TestESAggSource(metricExamples);
const metrics = source.getMetricFields();
expect(metrics.length).toBe(2);

expect(metrics[0].getRootName()).toEqual(sumFieldName);
expect(metrics[0].getName()).toEqual('sum_of_myFieldGettingSummed');
expect(await metrics[0].getLabel()).toEqual('my custom label');

expect(metrics[1].getName()).toEqual('doc_count');
expect(await metrics[1].getLabel()).toEqual('count');
});

it('getMetrics should be identical to getFields', async () => {
const source = new TestESAggSource(metricExamples);
const metrics = source.getMetricFields();
const fields = await source.getFields();

const getFieldMeta = async (field: IField) => {
const esAggField = field as IESAggField; // this ensures we can downcast correctly.
return {
name: esAggField.getName(),
label: await esAggField.getLabel(),
esDoc: esAggField.getRootName(),
};
};

const metricsFieldMeta = await Promise.all(metrics.map(getFieldMeta));
const fieldsFieldMeta = await Promise.all(fields.map(getFieldMeta));

expect(_.isEqual(metricsFieldMeta, fieldsFieldMeta)).toEqual(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

import { AbstractESAggSource } from '../es_agg_source';
import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types';
import { GRID_RESOLUTION } from '../../../../common/constants';

export class ESGeoGridSource extends AbstractESAggSource {
constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown);
getGridResolution(): GRID_RESOLUTION;
getGeoGridPrecision(zoom: number): number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_sty
import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property';
import { DataRequestAbortError } from '../../util/data_request';

const MAX_GEOTILE_LEVEL = 29;
export const MAX_GEOTILE_LEVEL = 29;

export class ESGeoGridSource extends AbstractESAggSource {
static type = ES_GEO_GRID;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
jest.mock('../../../kibana_services', () => {});
jest.mock('ui/new_platform');

import { ESGeoGridSource } from './es_geo_grid_source';
import { ES_GEO_GRID, GRID_RESOLUTION, RENDER_AS } from '../../../../common/constants';

describe('ESGeoGridSource', () => {
const geogridSource = new ESGeoGridSource(
{
id: 'foobar',
indexPatternId: 'fooIp',
geoField: 'bar',
metrics: [],
resolution: GRID_RESOLUTION.COARSE,
type: ES_GEO_GRID,
requestType: RENDER_AS.HEATMAP,
},
{}
);

describe('getGridResolution', () => {
it('should echo gridResoltuion', () => {
expect(geogridSource.getGridResolution()).toBe(GRID_RESOLUTION.COARSE);
});
});

describe('getGeoGridPrecision', () => {
it('should clamp geo-grid derived zoom to max geotile level supported by ES', () => {
expect(geogridSource.getGeoGridPrecision(29)).toBe(29);
});

it('should use heuristic to derive precision', () => {
expect(geogridSource.getGeoGridPrecision(10)).toBe(12);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
ES_GEO_FIELD_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT,
SORT_ORDER,
CATEGORICAL_DATA_TYPES,
} from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
Expand Down Expand Up @@ -135,49 +134,6 @@ export class ESSearchSource extends AbstractESSource {
);
}

async getNumberFields() {
try {
const indexPattern = await this.getIndexPattern();
return indexPattern.fields.getByType('number').map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
return [];
}
}

async getDateFields() {
try {
const indexPattern = await this.getIndexPattern();
return indexPattern.fields.getByType('date').map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
return [];
}
}

async getCategoricalFields() {
try {
const indexPattern = await this.getIndexPattern();

const aggFields = [];
CATEGORICAL_DATA_TYPES.forEach(dataType => {
indexPattern.fields.getByType(dataType).forEach(field => {
if (field.aggregatable) {
aggFields.push(field);
}
});
});
return aggFields.map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
//error surfaces in the LayerTOC UI
return [];
}
}

async getFields() {
try {
const indexPattern = await this.getIndexPattern();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,27 @@ const metricExamples = [
];

describe('getMetricFields', () => {
it('should add default "count" metric when no metrics are provided', async () => {
it('should override name and label of count metric', async () => {
const source = new ESTermSource({
indexPatternTitle: indexPatternTitle,
term: termFieldName,
});
const metrics = source.getMetricFields();
expect(metrics.length).toBe(1);

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

it('should remove incomplete metric configurations', async () => {
it('should override name and label of sum metric', async () => {
const source = new ESTermSource({
indexPatternTitle: indexPatternTitle,
term: termFieldName,
metrics: metricExamples,
});
const metrics = source.getMetricFields();
expect(metrics.length).toBe(2);

expect(metrics[0].getAggType()).toEqual('sum');
expect(metrics[0].getRootName()).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');
});
Expand Down
12 changes: 0 additions & 12 deletions x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,7 @@ export class AbstractVectorSource extends AbstractSource {
return null;
}

async getDateFields() {
return [];
}

async getNumberFields() {
return [];
}

async getFields() {
return [...(await this.getDateFields()), ...(await this.getNumberFields())];
}

async getCategoricalFields() {
return [];
}

Expand Down
Loading

0 comments on commit 87c05d9

Please sign in to comment.