diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx index b8ef8b7689627..e1cd8fbe3b551 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx @@ -115,6 +115,12 @@ const initialState: IndexPatternPrivateState = { aggregatable: true, searchable: true, }, + { + name: 'client', + type: 'ip', + aggregatable: true, + searchable: true, + }, ], }, '2': { @@ -375,6 +381,10 @@ describe('IndexPattern Data Panel', () => { name: 'source', type: 'string', }, + { + name: 'client', + type: 'ip', + }, ], }), }); @@ -423,6 +433,7 @@ describe('IndexPattern Data Panel', () => { expect(wrapper.find(FieldItem).map(fieldItem => fieldItem.prop('field').name)).toEqual([ 'bytes', + 'client', 'memory', 'source', 'timestamp', @@ -487,6 +498,7 @@ describe('IndexPattern Data Panel', () => { expect(wrapper.find(FieldItem).map(fieldItem => fieldItem.prop('field').name)).toEqual([ 'bytes', + 'client', 'memory', 'source', 'timestamp', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx index 33cd35eec8a25..f14c9e311cb09 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx @@ -46,7 +46,7 @@ function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { return fieldA.name.localeCompare(fieldB.name, undefined, { sensitivity: 'base' }); } -const supportedFieldTypes = ['string', 'number', 'boolean', 'date']; +const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip']); const PAGINATION_SIZE = 50; const fieldTypeNames: Record = { @@ -54,6 +54,7 @@ const fieldTypeNames: Record = { number: i18n.translate('xpack.lens.datatypes.number', { defaultMessage: 'number' }), boolean: i18n.translate('xpack.lens.datatypes.boolean', { defaultMessage: 'boolean' }), date: i18n.translate('xpack.lens.datatypes.date', { defaultMessage: 'date' }), + ip: i18n.translate('xpack.lens.datatypes.ipAddress', { defaultMessage: 'IP' }), }; function isSingleEmptyLayer(layerMap: IndexPatternPrivateState['layers']) { @@ -239,7 +240,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ ); const displayedFields = allFields.filter(field => { - if (!supportedFieldTypes.includes(field.type)) { + if (!supportedFieldTypes.has(field.type)) { return false; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.test.tsx index 741fbc5f43ad6..8fd72a790b38e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.test.tsx @@ -15,33 +15,40 @@ import React from 'react'; import { FieldIcon } from './field_icon'; describe('FieldIcon', () => { - it('should render numeric icons', () => { + it('should render icons', () => { expect(shallow()).toMatchInlineSnapshot(` - - `); + + `); expect(shallow()).toMatchInlineSnapshot(` - - `); + + `); expect(shallow()).toMatchInlineSnapshot(` - - `); + + `); expect(shallow()).toMatchInlineSnapshot(` + + `); + expect(shallow()).toMatchInlineSnapshot(` `); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.tsx index 04848df198e9e..d68ee0b82f28e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_icon.tsx @@ -17,6 +17,7 @@ function getIconForDataType(dataType: string) { const icons: Partial>> = { boolean: 'invert', date: 'calendar', + ip: 'storage', }; return icons[dataType] || ICON_TYPES.find(t => t === dataType) || 'empty'; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index a5b03c84f5cdd..87ef874dece66 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -100,7 +100,8 @@ export function FieldItem(props: FieldItemProps) { (field.type !== 'number' && field.type !== 'string' && field.type !== 'date' && - field.type !== 'boolean') + field.type !== 'boolean' && + field.type !== 'ip') ) { return; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx index 1040a26c3f7c4..970b781dec6ea 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx @@ -140,6 +140,19 @@ describe('terms', () => { isBucketed: true, scale: 'ordinal', }); + + expect( + termsOperation.getPossibleOperationForField({ + aggregatable: true, + searchable: true, + name: 'test', + type: 'ip', + }) + ).toEqual({ + dataType: 'ip', + isBucketed: true, + scale: 'ordinal', + }); }); it('should not return an operation if restrictions prevent terms', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx index 52b27f85fb495..6e9839dd1164f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx @@ -37,6 +37,7 @@ function isSortableByColumn(column: IndexPatternColumn) { } const DEFAULT_SIZE = 3; +const supportedTypes = new Set(['string', 'boolean', 'ip']); export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { operationType: 'terms'; @@ -54,11 +55,11 @@ export const termsOperation: OperationDefinition = { }), getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( - (type === 'string' || type === 'boolean') && + supportedTypes.has(type) && aggregatable && (!aggregationRestrictions || aggregationRestrictions.terms) ) { - return { dataType: type, isBucketed: true, scale: 'ordinal' }; + return { dataType: type as DataType, isBucketed: true, scale: 'ordinal' }; } }, isTransferable: (column, newIndexPattern) => { @@ -66,7 +67,7 @@ export const termsOperation: OperationDefinition = { return Boolean( newField && - newField.type === 'string' && + supportedTypes.has(newField.type) && newField.aggregatable && (!newField.aggregationRestrictions || newField.aggregationRestrictions.terms) ); diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 7945d439f75cd..217a694a1861c 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -187,7 +187,7 @@ export interface DatasourceLayerPanelProps { layerId: string; } -export type DataType = 'string' | 'number' | 'date' | 'boolean'; +export type DataType = 'string' | 'number' | 'date' | 'boolean' | 'ip'; // An operation represents a column in a table, not any information // about how the column was created such as whether it is a sum or average. diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index 0bc316c96287d..ff5f7eb08f2db 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -108,6 +108,7 @@ export function getScaleType(metadata: OperationMetadata | null, defaultScale: S switch (metadata.dataType) { case 'boolean': case 'string': + case 'ip': return ScaleType.Ordinal; case 'date': return ScaleType.Time; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts index 41abf99e987a5..5ff8b3ee6ba16 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts @@ -117,17 +117,17 @@ describe('xy_suggestions', () => { expect(rest).toHaveLength(0); expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` - Array [ - Object { - "seriesType": "area_stacked", - "splitAccessor": "aaa", - "x": "date", - "y": Array [ - "bytes", - ], - }, - ] - `); + Array [ + Object { + "seriesType": "area_stacked", + "splitAccessor": "aaa", + "x": "date", + "y": Array [ + "bytes", + ], + }, + ] + `); }); test('does not suggest multiple splits', () => { @@ -161,18 +161,18 @@ describe('xy_suggestions', () => { expect(rest).toHaveLength(0); expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` - Array [ - Object { - "seriesType": "area_stacked", - "splitAccessor": "product", - "x": "date", - "y": Array [ - "price", - "quantity", - ], - }, - ] - `); + Array [ + Object { + "seriesType": "area_stacked", + "splitAccessor": "product", + "x": "date", + "y": Array [ + "price", + "quantity", + ], + }, + ] + `); }); test('uses datasource provided title if available', () => { @@ -392,32 +392,33 @@ describe('xy_suggestions', () => { }); expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` - Array [ - Object { - "seriesType": "bar_stacked", - "splitAccessor": "ddd", - "x": "quantity", - "y": Array [ - "price", - ], - }, - ] - `); + Array [ + Object { + "seriesType": "bar_stacked", + "splitAccessor": "ddd", + "x": "quantity", + "y": Array [ + "price", + ], + }, + ] + `); }); - test('handles unbucketed suggestions', () => { - (generateId as jest.Mock).mockReturnValueOnce('eee'); + test('handles ip', () => { + (generateId as jest.Mock).mockReturnValueOnce('ddd'); const [suggestion] = getSuggestions({ table: { isMultiRow: true, columns: [ - numCol('num votes'), + numCol('quantity'), { - columnId: 'mybool', + columnId: 'myip', operation: { - dataType: 'boolean', - isBucketed: false, - label: 'Yes / No', + dataType: 'ip', + label: 'Top 5 myip', + isBucketed: true, + scale: 'ordinal', }, }, ], @@ -430,13 +431,48 @@ describe('xy_suggestions', () => { Array [ Object { "seriesType": "bar_stacked", - "splitAccessor": "eee", - "x": "mybool", + "splitAccessor": "ddd", + "x": "myip", "y": Array [ - "num votes", + "quantity", ], }, ] `); }); + + test('handles unbucketed suggestions', () => { + (generateId as jest.Mock).mockReturnValueOnce('eee'); + const [suggestion] = getSuggestions({ + table: { + isMultiRow: true, + columns: [ + numCol('num votes'), + { + columnId: 'mybool', + operation: { + dataType: 'boolean', + isBucketed: false, + label: 'Yes / No', + }, + }, + ], + layerId: 'first', + changeType: 'unchanged', + }, + }); + + expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` + Array [ + Object { + "seriesType": "bar_stacked", + "splitAccessor": "eee", + "x": "mybool", + "y": Array [ + "num votes", + ], + }, + ] + `); + }); }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts index e447c9d7eb366..fbbb48ba08eb2 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts @@ -21,8 +21,9 @@ import { generateId } from '../id_generator'; const columnSortOrder = { date: 0, string: 1, - boolean: 2, - number: 3, + ip: 2, + boolean: 3, + number: 4, }; function getIconForSeries(type: SeriesType): EuiIconType {