diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 98679a8f24d16..0a81ca0222b0a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -76,5 +76,3 @@ export { EsQuerySortValue, SortDirection, } from '../../../../../plugins/data/public'; -// @ts-ignore -export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx index f788347ac016c..8c55622e4c604 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx @@ -46,9 +46,10 @@ import { IUiSettingsClient } from 'kibana/public'; import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme'; import { Subscription } from 'rxjs'; import { getServices } from '../../../kibana_services'; +import { Chart as IChart } from '../helpers/point_series'; export interface DiscoverHistogramProps { - chartData: any; + chartData: IChart; timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; } @@ -163,7 +164,7 @@ export class DiscoverHistogram extends Component { - const xAxisFormat = this.props.chartData.xAxisFormat.params.pattern; + const xAxisFormat = this.props.chartData.xAxisFormat.params!.pattern; return moment(val).format(xAxisFormat); }; @@ -208,18 +209,19 @@ export class DiscoverHistogram extends Component domainStart ? domainStart : data[0].x; + const domainMin = data[0]?.x > domainStart ? domainStart : data[0]?.x; const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue; const xDomain = { diff --git a/src/legacy/ui/public/agg_response/point_series/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/index.ts similarity index 100% rename from src/legacy/ui/public/agg_response/point_series/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts new file mode 100644 index 0000000000000..02dd024b09812 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uniq } from 'lodash'; +import { Duration, Moment } from 'moment'; +import { Unit } from '@elastic/datemath'; + +import { SerializedFieldFormat } from '../../../../../../../../plugins/expressions/common/types'; + +export interface Column { + id: string; + name: string; +} + +export interface Row { + [key: string]: number | 'NaN'; +} + +export interface Table { + columns: Column[]; + rows: Row[]; +} + +interface HistogramParams { + date: true; + interval: Duration; + intervalESValue: number; + intervalESUnit: Unit; + format: string; + bounds: { + min: Moment; + max: Moment; + }; +} +export interface Dimension { + accessor: 0 | 1; + format: SerializedFieldFormat<{ pattern: string }>; +} + +export interface Dimensions { + x: Dimension & { params: HistogramParams }; + y: Dimension; +} + +interface Ordered { + date: true; + interval: Duration; + intervalESUnit: string; + intervalESValue: number; + min: Moment; + max: Moment; +} +export interface Chart { + values: Array<{ + x: number; + y: number; + }>; + xAxisOrderedValues: number[]; + xAxisFormat: Dimension['format']; + xAxisLabel: Column['name']; + yAxisLabel?: Column['name']; + ordered: Ordered; +} + +export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { + const { x, y } = dimensions; + const xAccessor = table.columns[x.accessor].id; + const yAccessor = table.columns[y.accessor].id; + const chart = {} as Chart; + + chart.xAxisOrderedValues = uniq(table.rows.map(r => r[xAccessor] as number)); + chart.xAxisFormat = x.format; + chart.xAxisLabel = table.columns[x.accessor].name; + + const { intervalESUnit, intervalESValue, interval, bounds } = x.params; + chart.ordered = { + date: true, + interval, + intervalESUnit, + intervalESValue, + min: bounds.min, + max: bounds.max, + }; + + chart.yAxisLabel = table.columns[y.accessor].name; + + chart.values = table.rows + .filter(row => row && row[yAccessor] !== 'NaN') + .map(row => ({ + x: row[xAccessor] as number, + y: row[yAccessor] as number, + })); + + return chart; +}; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js index 0c19c10841535..04ccb67ec7e25 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js @@ -17,7 +17,8 @@ * under the License. */ -import { buildPointSeriesData, getServices } from '../../kibana_services'; +import { getServices } from '../../kibana_services'; +import { buildPointSeriesData } from './helpers'; function tableResponseHandler(table, dimensions) { const converted = { tables: [] }; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index bceb3fa7eef8a..0a026a5e0c310 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -46,7 +46,6 @@ import './discover/legacy'; import './visualize/legacy'; import './management'; import './dev_tools'; -import 'ui/agg_response'; import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public'; import 'leaflet'; import { localApplicationService } from './local_application_service'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts index da16a38deba9f..c04ffa506eb04 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -19,8 +19,3 @@ import { search } from '../../../../plugins/data/public'; export const { tabifyAggResponse, tabifyGetColumns } = search; - -// @ts-ignore -export { buildHierarchicalData } from 'ui/agg_response/hierarchical/build_hierarchical_data'; -// @ts-ignore -export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js deleted file mode 100644 index 3574fb232883d..0000000000000 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { aggResponseIndex } from 'ui/agg_response'; - -import { vislibSeriesResponseHandler } from '../response_handler'; - -/** - * TODO: Fix these tests if still needed - * - * All these tests were not being run in master or prodiced false positive results - * Fixing them would require changes to the response handler logic. - */ - -describe.skip('Basic Response Handler', function() { - beforeEach(ngMock.module('kibana')); - - it('returns empty object if conversion failed', () => { - const data = vislibSeriesResponseHandler({}); - expect(data).to.not.be.an('undefined'); - expect(data).to.equal({}); - }); - - it('returns empty object if no data was found', () => { - const data = vislibSeriesResponseHandler({ - columns: [{ id: '1', title: '1', aggConfig: {} }], - rows: [], - }); - expect(data).to.not.be.an('undefined'); - expect(data.rows).to.equal([]); - }); -}); - -describe.skip('renderbot#buildChartData', function() { - describe('for hierarchical vis', function() { - it('defers to hierarchical aggResponse converter', function() { - const football = {}; - const stub = sinon.stub(aggResponseIndex, 'hierarchical').returns(football); - expect(vislibSeriesResponseHandler(football)).to.be(football); - expect(stub).to.have.property('callCount', 1); - expect(stub.firstCall.args[1]).to.be(football); - }); - }); - - describe('for point plot', function() { - it('calls tabify to simplify the data into a table', function() { - const football = { tables: [], hits: { total: 1 } }; - const stub = sinon.stub(aggResponseIndex, 'tabify').returns(football); - expect(vislibSeriesResponseHandler(football)).to.eql({ rows: [], hits: 1 }); - expect(stub).to.have.property('callCount', 1); - expect(stub.firstCall.args[1]).to.be(football); - }); - - it('returns a single chart if the tabify response contains only a single table', function() { - const chart = { hits: 1, rows: [], columns: [] }; - const esResp = { hits: { total: 1 } }; - const tabbed = { tables: [{}] }; - - sinon.stub(aggResponseIndex, 'tabify').returns(tabbed); - expect(vislibSeriesResponseHandler(esResp)).to.eql(chart); - }); - - it('converts table groups into rows/columns wrappers for charts', function() { - const converter = sinon.stub().returns('chart'); - const esResp = { hits: { total: 1 } }; - const tables = [{}, {}, {}, {}]; - - sinon.stub(aggResponseIndex, 'tabify').returns({ - tables: [ - { - aggConfig: { params: { row: true } }, - tables: [ - { - aggConfig: { params: { row: false } }, - tables: [tables[0]], - }, - { - aggConfig: { params: { row: false } }, - tables: [tables[1]], - }, - ], - }, - { - aggConfig: { params: { row: true } }, - tables: [ - { - aggConfig: { params: { row: false } }, - tables: [tables[2]], - }, - { - aggConfig: { params: { row: false } }, - tables: [tables[3]], - }, - ], - }, - ], - }); - - const chartData = vislibSeriesResponseHandler(esResp); - - // verify tables were converted - expect(converter).to.have.property('callCount', 4); - expect(converter.args[0][1]).to.be(tables[0]); - expect(converter.args[1][1]).to.be(tables[1]); - expect(converter.args[2][1]).to.be(tables[2]); - expect(converter.args[3][1]).to.be(tables[3]); - - expect(chartData).to.have.property('rows'); - expect(chartData.rows).to.have.length(2); - chartData.rows.forEach(function(row) { - expect(row).to.have.property('columns'); - expect(row.columns).to.eql(['chart', 'chart']); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts similarity index 80% rename from src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts index 21a937bf1fb66..475555f3a15f3 100644 --- a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts @@ -17,24 +17,26 @@ * under the License. */ -import { buildHierarchicalData } from './build_hierarchical_data'; +import { buildHierarchicalData, Dimensions, Dimension } from './build_hierarchical_data'; +import { Table, TableParent } from '../../types'; -function tableVisResponseHandler(table, dimensions) { - const converted = { +function tableVisResponseHandler(table: Table, dimensions: Dimensions) { + const converted: { + tables: Array; + } = { tables: [], }; const split = dimensions.splitColumn || dimensions.splitRow; if (split) { - converted.direction = dimensions.splitRow ? 'row' : 'column'; const splitColumnIndex = split[0].accessor; const splitColumn = table.columns[splitColumnIndex]; - const splitMap = {}; + const splitMap: { [key: string]: number } = {}; let splitIndex = 0; table.rows.forEach((row, rowIndex) => { - const splitValue = row[splitColumn.id]; + const splitValue = row[splitColumn.id] as string; if (!splitMap.hasOwnProperty(splitValue)) { splitMap[splitValue] = splitIndex++; @@ -46,8 +48,8 @@ function tableVisResponseHandler(table, dimensions) { column: splitColumnIndex, row: rowIndex, table, - tables: [], - }; + tables: [] as Table[], + } as any; tableGroup.tables.push({ $parent: tableGroup, @@ -59,34 +61,30 @@ function tableVisResponseHandler(table, dimensions) { } const tableIndex = splitMap[splitValue]; - converted.tables[tableIndex].tables[0].rows.push(row); + (converted.tables[tableIndex] as TableParent).tables![0].rows.push(row); }); } else { converted.tables.push({ columns: table.columns, rows: table.rows, - }); + } as Table); } return converted; } -jest.mock('ui/new_platform'); -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: jest.fn().mockReturnValue({ - get: jest.fn().mockReturnValue('KQL'), - }), -})); -jest.mock('ui/visualize/loader/pipeline_helpers/utilities', () => ({ - getFormat: jest.fn(() => ({ - convert: jest.fn(v => v), +jest.mock('../../../services', () => ({ + getFormatService: jest.fn(() => ({ + deserialize: () => ({ + convert: jest.fn(v => JSON.stringify(v)), + }), })), })); describe('buildHierarchicalData convertTable', () => { describe('metric only', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(() => { const tabifyResponse = { @@ -94,11 +92,11 @@ describe('buildHierarchicalData convertTable', () => { rows: [{ 'col-0-agg_1': 412032 }], }; dimensions = { - metric: { accessor: 0 }, + metric: { accessor: 0 } as Dimension, }; const tableGroup = tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the slices with one child to a consistent label', () => { @@ -118,8 +116,8 @@ describe('buildHierarchicalData convertTable', () => { }); describe('threeTermBuckets', () => { - let dimensions; - let tables; + let dimensions: Dimensions; + let tables: TableParent[]; beforeEach(async () => { const tabifyResponse = { @@ -231,60 +229,60 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - splitRow: [{ accessor: 0 }], - metric: { accessor: 5 }, - buckets: [{ accessor: 2 }, { accessor: 4 }], + splitRow: [{ accessor: 0 } as Dimension], + metric: { accessor: 5 } as Dimension, + buckets: [{ accessor: 2 }, { accessor: 4 }] as Dimension[], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - tables = tableGroup.tables; + tables = tableGroup.tables as TableParent[]; }); it('should set the correct hits attribute for each of the results', () => { tables.forEach(t => { - const results = buildHierarchicalData(t.tables[0], dimensions); + const results = buildHierarchicalData(t.tables![0], dimensions); expect(results).toHaveProperty('hits'); expect(results.hits).toBe(4); }); }); it('should set the correct names for each of the results', () => { - const results0 = buildHierarchicalData(tables[0].tables[0], dimensions); + const results0 = buildHierarchicalData(tables[0].tables![0], dimensions); expect(results0).toHaveProperty('names'); expect(results0.names).toHaveLength(5); - const results1 = buildHierarchicalData(tables[1].tables[0], dimensions); + const results1 = buildHierarchicalData(tables[1].tables![0], dimensions); expect(results1).toHaveProperty('names'); expect(results1.names).toHaveLength(5); - const results2 = buildHierarchicalData(tables[2].tables[0], dimensions); + const results2 = buildHierarchicalData(tables[2].tables![0], dimensions); expect(results2).toHaveProperty('names'); expect(results2.names).toHaveLength(4); }); it('should set the parent of the first item in the split', () => { - const results0 = buildHierarchicalData(tables[0].tables[0], dimensions); + const results0 = buildHierarchicalData(tables[0].tables![0], dimensions); expect(results0).toHaveProperty('slices'); expect(results0.slices).toHaveProperty('children'); expect(results0.slices.children).toHaveLength(2); - expect(results0.slices.children[0].rawData.table.$parent).toHaveProperty('key', 'png'); + expect(results0.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'png'); - const results1 = buildHierarchicalData(tables[1].tables[0], dimensions); + const results1 = buildHierarchicalData(tables[1].tables![0], dimensions); expect(results1).toHaveProperty('slices'); expect(results1.slices).toHaveProperty('children'); expect(results1.slices.children).toHaveLength(2); - expect(results1.slices.children[0].rawData.table.$parent).toHaveProperty('key', 'css'); + expect(results1.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'css'); - const results2 = buildHierarchicalData(tables[2].tables[0], dimensions); + const results2 = buildHierarchicalData(tables[2].tables![0], dimensions); expect(results2).toHaveProperty('slices'); expect(results2.slices).toHaveProperty('children'); expect(results2.slices.children).toHaveLength(2); - expect(results2.slices.children[0].rawData.table.$parent).toHaveProperty('key', 'html'); + expect(results2.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'html'); }); }); describe('oneHistogramBucket', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(async () => { const tabifyResponse = { @@ -302,11 +300,11 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - metric: { accessor: 1 }, - buckets: [{ accessor: 0, params: { field: 'bytes', interval: 8192 } }], + metric: { accessor: 1 } as Dimension, + buckets: [{ accessor: 0 } as Dimension], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the hits attribute for the results', () => { @@ -320,8 +318,8 @@ describe('buildHierarchicalData convertTable', () => { }); describe('oneRangeBucket', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(async () => { const tabifyResponse = { @@ -335,11 +333,11 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - metric: { accessor: 1 }, - buckets: [{ accessor: 0, format: { id: 'range', params: { id: 'agg_2' } } }], + metric: { accessor: 1 } as Dimension, + buckets: [{ accessor: 0, format: { id: 'range', params: { id: 'agg_2' } } } as Dimension], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the hits attribute for the results', () => { @@ -348,13 +346,13 @@ describe('buildHierarchicalData convertTable', () => { expect(results).toHaveProperty('slices'); expect(results.slices).toHaveProperty('children'); expect(results).toHaveProperty('names'); - // expect(results.names).toHaveLength(2); + expect(results.names).toHaveLength(2); }); }); describe('oneFilterBucket', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(async () => { const tabifyResponse = { @@ -368,15 +366,15 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - metric: { accessor: 1 }, + metric: { accessor: 1 } as Dimension, buckets: [ { accessor: 0, }, - ], + ] as Dimension[], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the hits attribute for the results', () => { diff --git a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts similarity index 63% rename from src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts index dcc27e956b3f8..2c6d62ed084b5 100644 --- a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts @@ -18,11 +18,41 @@ */ import { toArray } from 'lodash'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { SerializedFieldFormat } from '../../../../../../../plugins/expressions/common/types'; +import { getFormatService } from '../../../services'; +import { Table } from '../../types'; -export const buildHierarchicalData = (table, { metric, buckets = [] }) => { - let slices; - const names = {}; +export interface Dimension { + accessor: number; + format: { + id?: string; + params?: SerializedFieldFormat; + }; +} + +export interface Dimensions { + metric: Dimension; + buckets?: Dimension[]; + splitRow?: Dimension[]; + splitColumn?: Dimension[]; +} + +interface Slice { + name: string; + size: number; + parent?: Slice; + children?: []; + rawData?: { + table: Table; + row: number; + column: number; + value: string | number | object; + }; +} + +export const buildHierarchicalData = (table: Table, { metric, buckets = [] }: Dimensions) => { + let slices: Slice[]; + const names: { [key: string]: string } = {}; const metricColumn = table.columns[metric.accessor]; const metricFieldFormatter = metric.format; @@ -30,25 +60,25 @@ export const buildHierarchicalData = (table, { metric, buckets = [] }) => { slices = [ { name: metricColumn.name, - size: table.rows[0][metricColumn.id], + size: table.rows[0][metricColumn.id] as number, }, ]; names[metricColumn.name] = metricColumn.name; } else { slices = []; table.rows.forEach((row, rowIndex) => { - let parent; + let parent: Slice; let dataLevel = slices; buckets.forEach(bucket => { const bucketColumn = table.columns[bucket.accessor]; const bucketValueColumn = table.columns[bucket.accessor + 1]; - const bucketFormatter = getFormat(bucket.format); + const bucketFormatter = getFormatService().deserialize(bucket.format); const name = bucketFormatter.convert(row[bucketColumn.id]); - const size = row[bucketValueColumn.id]; + const size = row[bucketValueColumn.id] as number; names[name] = name; - let slice = dataLevel.find(slice => slice.name === name); + let slice = dataLevel.find(dataLevelSlice => dataLevelSlice.name === name); if (!slice) { slice = { name, @@ -66,7 +96,7 @@ export const buildHierarchicalData = (table, { metric, buckets = [] }) => { } parent = slice; - dataLevel = slice.children; + dataLevel = slice.children as []; }); }); } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/index.ts similarity index 71% rename from src/legacy/ui/public/agg_response/point_series/__tests__/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/index.ts index 9c3e1c8180eb5..90924e79f6027 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/index.ts @@ -17,14 +17,5 @@ * under the License. */ -describe('Point Series Agg Response', function() { - require('./_main'); - require('./_add_to_siri'); - require('./_fake_x_aspect'); - require('./_get_aspects'); - require('./_get_point'); - require('./_get_series'); - require('./_init_x_axis'); - require('./_init_y_axis'); - require('./_ordered_date_axis'); -}); +export { buildPointSeriesData } from './point_series'; +export { buildHierarchicalData } from './hierarchical/build_hierarchical_data'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts new file mode 100644 index 0000000000000..e4fdd6bb71c00 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { addToSiri, Serie } from './_add_to_siri'; +import { Point } from './_get_point'; +import { Dimension } from './point_series'; + +describe('addToSiri', function() { + it('creates a new series the first time it sees an id', function() { + const series = new Map(); + const point = {} as Point; + const id = 'id'; + addToSiri(series, point, id, id, { id }); + + const expectedSerie = series.get(id) as Serie; + expect(series.has(id)).toBe(true); + expect(expectedSerie).toEqual(expect.any(Object)); + expect(expectedSerie.label).toBe(id); + expect(expectedSerie.values).toHaveLength(1); + expect(expectedSerie.values[0]).toBe(point); + }); + + it('adds points to existing series if id has been seen', function() { + const series = new Map(); + const id = 'id'; + + const point = {} as Point; + addToSiri(series, point, id, id, { id }); + + const point2 = {} as Point; + addToSiri(series, point2, id, id, { id }); + + expect(series.has(id)).toBe(true); + expect(series.get(id)).toEqual(expect.any(Object)); + expect(series.get(id).label).toBe(id); + expect(series.get(id).values).toHaveLength(2); + expect(series.get(id).values[0]).toBe(point); + expect(series.get(id).values[1]).toBe(point2); + }); + + it('allows overriding the series label', function() { + const series = new Map(); + const id = 'id'; + const label = 'label'; + const point = {} as Point; + addToSiri(series, point, id, label, { id }); + + expect(series.has(id)).toBe(true); + expect(series.get(id)).toEqual(expect.any(Object)); + expect(series.get(id).label).toBe(label); + expect(series.get(id).values).toHaveLength(1); + expect(series.get(id).values[0]).toBe(point); + }); + + it('correctly sets id and rawId', function() { + const series = new Map(); + const id = 'id-id2'; + + const point = {} as Point; + addToSiri(series, point, id, undefined, {} as Dimension['format']); + + expect(series.has(id)).toBe(true); + expect(series.get(id)).toEqual(expect.any(Object)); + expect(series.get(id).label).toBe(id); + expect(series.get(id).rawId).toBe(id); + expect(series.get(id).id).toBe('id2'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts new file mode 100644 index 0000000000000..5e5185d6c31ab --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Point } from './_get_point'; +import { Dimension } from './point_series'; + +export interface Serie { + id: string; + rawId: string; + label: string; + count: number; + values: Point[]; + format: Dimension['format']; + zLabel?: string; + zFormat?: Dimension['format']; +} + +export function addToSiri( + series: Map, + point: Point, + id: string, + yLabel: string | undefined | null, + yFormat: Dimension['format'], + zFormat?: Dimension['format'], + zLabel?: string +) { + id = id == null ? '' : id + ''; + + if (series.has(id)) { + (series.get(id) as Serie).values.push(point); + return; + } + + series.set(id, { + id: id.split('-').pop() as string, + rawId: id, + label: yLabel == null ? id : yLabel, + count: 0, + values: [point], + format: yFormat, + zLabel, + zFormat, + }); +} diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts similarity index 74% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts index 6c246d7f50897..43d4c3d7ca7c4 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts @@ -16,20 +16,17 @@ * specific language governing permissions and limitations * under the License. */ - -import expect from '@kbn/expect'; -import { makeFakeXAspect } from '../_fake_x_aspect'; +import { makeFakeXAspect } from './_fake_x_aspect'; describe('makeFakeXAspect', function() { it('creates an object that looks like an aspect', function() { const aspect = makeFakeXAspect(); - expect(aspect) - .to.have.property('accessor', -1) - .and.have.property('title', 'All docs') - .and.have.property('format') - .and.have.property('params'); + expect(aspect).toHaveProperty('accessor', -1); + expect(aspect).toHaveProperty('title', 'All docs'); + expect(aspect).toHaveProperty('format'); + expect(aspect).toHaveProperty('params'); - expect(aspect.params).to.have.property('defaultValue', '_all'); + expect(aspect.params).toHaveProperty('defaultValue', '_all'); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_fake_x_aspect.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts similarity index 88% rename from src/legacy/ui/public/agg_response/point_series/_fake_x_aspect.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts index 254a42baeddb0..1bffa4cceb5b0 100644 --- a/src/legacy/ui/public/agg_response/point_series/_fake_x_aspect.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts @@ -18,16 +18,17 @@ */ import { i18n } from '@kbn/i18n'; +import { Aspect } from './point_series'; export function makeFakeXAspect() { return { accessor: -1, - title: i18n.translate('common.ui.aggResponse.allDocsTitle', { + title: i18n.translate('visTypeVislib.aggResponse.allDocsTitle', { defaultMessage: 'All docs', }), params: { defaultValue: '_all', }, format: {}, - }; + } as Aspect; } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_aspects.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts similarity index 53% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_get_aspects.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts index fab5c2e290e7e..450b283abbed2 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_aspects.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts @@ -17,37 +17,37 @@ * under the License. */ -import expect from '@kbn/expect'; -import { getAspects } from '../_get_aspects'; +import { getAspects } from './_get_aspects'; +import { Dimension, Dimensions, Aspect } from './point_series'; +import { Table, Row } from '../../types'; describe('getAspects', function() { - let table; - let dimensions; + let table: Table; + let dimensions: Dimensions; - function validate(aspect, i) { - expect(aspect) - .to.be.an('object') - .and.have.property('accessor', i); + function validate(aspect: Aspect, i: string) { + expect(aspect).toEqual(expect.any(Object)); + expect(aspect).toHaveProperty('accessor', i); } - function init(group, x, y) { + function init(group: number, x: number | null, y: number) { table = { columns: [ - { id: '0', title: 'date' }, // date - { id: '1', title: 'date utc_time' }, // date - { id: '2', title: 'ext' }, // extension - { id: '3', title: 'geo.src' }, // extension - { id: '4', title: 'count' }, // count - { id: '5', title: 'avg bytes' }, // avg + { id: '0', name: 'date' }, // date + { id: '1', name: 'date utc_time' }, // date + { id: '2', name: 'ext' }, // extension + { id: '3', name: 'geo.src' }, // extension + { id: '4', name: 'count' }, // count + { id: '5', name: 'avg bytes' }, // avg ], - rows: [], - }; + rows: [] as Row[], + } as Table; dimensions = { - x: { accessor: x }, - y: { accessor: y }, - series: { accessor: group }, - }; + x: { accessor: x } as Dimension, + y: [{ accessor: y } as Dimension], + series: [{ accessor: group } as Dimension], + } as Dimensions; } it('produces an aspect object for each of the aspect types found in the columns', function() { @@ -55,8 +55,8 @@ describe('getAspects', function() { const aspects = getAspects(table, dimensions); validate(aspects.x[0], '0'); - validate(aspects.series[0], '1'); - validate(aspects.y[0], '2'); + validate(aspects.series![0], '1'); + validate(aspects.y![0], '2'); }); it('creates a fake x aspect if the column does not exist', function() { @@ -64,9 +64,8 @@ describe('getAspects', function() { const aspects = getAspects(table, dimensions); - expect(aspects.x[0]) - .to.be.an('object') - .and.have.property('accessor', -1) - .and.have.property('title'); + expect(aspects.x[0]).toEqual(expect.any(Object)); + expect(aspects.x[0]).toHaveProperty('accessor', -1); + expect(aspects.x[0]).toHaveProperty('title'); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_get_aspects.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts similarity index 72% rename from src/legacy/ui/public/agg_response/point_series/_get_aspects.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts index fe74d8566c0e7..29134409ddd5f 100644 --- a/src/legacy/ui/public/agg_response/point_series/_get_aspects.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts @@ -18,20 +18,22 @@ */ import { makeFakeXAspect } from './_fake_x_aspect'; +import { Dimensions, Aspects } from './point_series'; +import { Table } from '../../types'; /** * Identify and group the columns based on the aspect of the pointSeries * they represent. * - * @param {array} columns - the list of columns * @return {object} - an object with a key for each aspect (see map). The values - * may be undefined, a single aspect, or an array of aspects. + * may be undefined or an array of aspects. */ -export function getAspects(table, dimensions) { - const aspects = {}; - Object.keys(dimensions).forEach(name => { - const dimension = Array.isArray(dimensions[name]) ? dimensions[name] : [dimensions[name]]; - dimension.forEach(d => { +export function getAspects(table: Table, dimensions: Dimensions) { + const aspects: Partial = {}; + (Object.keys(dimensions) as Array).forEach(name => { + const dimension = dimensions[name]; + const dimensionList = Array.isArray(dimension) ? dimension : [dimension]; + dimensionList.forEach(d => { if (!d) { return; } @@ -42,7 +44,7 @@ export function getAspects(table, dimensions) { if (!aspects[name]) { aspects[name] = []; } - aspects[name].push({ + aspects[name]!.push({ accessor: column.id, column: d.accessor, title: column.name, @@ -56,5 +58,5 @@ export function getAspects(table, dimensions) { aspects.x = [makeFakeXAspect()]; } - return aspects; + return aspects as Aspects; } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts new file mode 100644 index 0000000000000..0c79c5b263cea --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IFieldFormatsRegistry } from '../../../../../../../plugins/data/common'; +import { getPoint } from './_get_point'; +import { setFormatService } from '../../../services'; +import { Aspect } from './point_series'; +import { Table, Row, Column } from '../../types'; + +describe('getPoint', function() { + let deserialize: IFieldFormatsRegistry['deserialize']; + + beforeAll(() => { + deserialize = jest.fn(() => ({ + convert: jest.fn(v => v), + })) as any; + + setFormatService({ + deserialize, + } as any); + }); + + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 4, '1': 'NaN', '2': 6 }, + ], + } as Table; + + describe('Without series aspect', function() { + let seriesAspect: undefined; + let xAspect: Aspect; + let yAspect: Aspect; + + beforeEach(function() { + xAspect = { accessor: '0' } as Aspect; + yAspect = { accessor: '1', title: 'Y' } as Aspect; + }); + + it('properly unwraps values', function() { + const row = table.rows[0]; + const zAspect = { accessor: '2' } as Aspect; + const point = getPoint(table, xAspect, seriesAspect, row, 0, yAspect, zAspect); + + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('y', 2); + expect(point).toHaveProperty('z', 3); + expect(point).toHaveProperty('series', yAspect.title); + }); + + it('ignores points with a y value of NaN', function() { + const row = table.rows[1]; + const point = getPoint(table, xAspect, seriesAspect, row, 1, yAspect); + expect(point).toBe(void 0); + }); + }); + + describe('With series aspect', function() { + let row: Row; + let xAspect: Aspect; + let yAspect: Aspect; + + beforeEach(function() { + row = table.rows[0]; + xAspect = { accessor: '0' } as Aspect; + yAspect = { accessor: '2' } as Aspect; + }); + + it('properly unwraps values', function() { + const seriesAspect = [{ accessor: '1' } as Aspect]; + const point = getPoint(table, xAspect, seriesAspect, row, 0, yAspect); + + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('series', '2'); + expect(point).toHaveProperty('y', 3); + }); + + it('should call deserialize', function() { + const seriesAspect = [ + { accessor: '1', format: { id: 'number', params: { pattern: '$' } } } as Aspect, + ]; + getPoint(table, xAspect, seriesAspect, row, 0, yAspect); + + expect(deserialize).toHaveBeenCalledWith(seriesAspect[0].format); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_response/point_series/_get_point.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts similarity index 69% rename from src/legacy/ui/public/agg_response/point_series/_get_point.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts index 11e639f3f54a8..3fc13eb0c04b5 100644 --- a/src/legacy/ui/public/agg_response/point_series/_get_point.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts @@ -17,19 +17,55 @@ * under the License. */ -import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; +import { getFormatService } from '../../../services'; +import { Aspect } from './point_series'; +import { Table, Row } from '../../types'; -export function getPoint(table, x, series, yScale, row, rowIndex, y, z) { +type RowValue = number | string | object | 'NaN'; +interface Raw { + table: Table; + column: number | undefined; + row: number | undefined; + value?: RowValue; +} +export interface Point { + x: RowValue | '_all'; + y: RowValue; + z?: RowValue; + extraMetrics: []; + seriesRaw?: Raw; + xRaw: Raw; + yRaw: Raw; + zRaw?: Raw; + tableRaw?: { + table: Table; + column: number; + row: number; + value: number; + title: string; + }; + parent: Aspect | null; + series?: string; + seriesId?: string; +} +export function getPoint( + table: Table, + x: Aspect, + series: Aspect[] | undefined, + row: Row, + rowIndex: number, + y: Aspect, + z?: Aspect +): Point | undefined { const xRow = x.accessor === -1 ? '_all' : row[x.accessor]; const yRow = row[y.accessor]; const zRow = z && row[z.accessor]; - const point = { + const point: Point = { x: xRow, y: yRow, z: zRow, extraMetrics: [], - yScale: yScale, seriesRaw: series && { table, column: series[0].column, @@ -71,10 +107,9 @@ export function getPoint(table, x, series, yScale, row, rowIndex, y, z) { } if (series) { - const seriesArray = series.length ? series : [series]; - point.series = seriesArray + point.series = series .map(s => { - const fieldFormatter = getFormat(s.format); + const fieldFormatter = getFormatService().deserialize(s.format); return fieldFormatter.convert(row[s.accessor]); }) .join(' - '); @@ -84,9 +119,5 @@ export function getPoint(table, x, series, yScale, row, rowIndex, y, z) { point.series = y.title; } - if (yScale) { - point.y *= yScale; - } - return point; } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts new file mode 100644 index 0000000000000..6b94b9de8e15f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts @@ -0,0 +1,281 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getSeries } from './_get_series'; +import { setFormatService } from '../../../services'; +import { Chart, Aspect } from './point_series'; +import { Table, Column } from '../../types'; +import { Serie } from './_add_to_siri'; +import { Point } from './_get_point'; + +describe('getSeries', function() { + beforeAll(() => { + setFormatService({ + deserialize: () => ({ + convert: jest.fn(v => v), + }), + } as any); + }); + + it('produces a single series with points for each row', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: '0' }], + y: [{ accessor: '1', title: 'y' }], + z: [{ accessor: '2' }], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(1); + + const siri = series[0]; + + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', chart.aspects.y[0].title); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(5); + + siri.values.forEach(point => { + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('y', 2); + expect(point).toHaveProperty('z', 3); + }); + }); + + it('adds the seriesId to each point', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + + series[0].values.forEach(point => { + expect(point).toHaveProperty('seriesId', '1'); + }); + + series[1].values.forEach(point => { + expect(point).toHaveProperty('seriesId', '2'); + }); + }); + + it('produces multiple series if there are multiple y aspects', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(2); + + series.forEach(function(siri: Serie, i: number) { + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', '' + i); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(5); + + siri.values.forEach(function(point: Point) { + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('y', i + 2); + }); + }); + }); + + it('produces multiple series if there is a series aspect', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: -1 } as Aspect], + series: [{ accessor: '0' }], + y: [{ accessor: '1', title: '0' }], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(2); + + series.forEach(function(siri: Serie, i: number) { + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', '' + i); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(3); + + siri.values.forEach(function(point: Point) { + expect(point).toHaveProperty('y', 2); + }); + }); + }); + + it('produces multiple series if there is a series aspect and multiple y aspects', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 0, '1': 3, '2': 4 }, + { '0': 1, '1': 3, '2': 4 }, + { '0': 0, '1': 3, '2': 4 }, + { '0': 1, '1': 3, '2': 4 }, + { '0': 0, '1': 3, '2': 4 }, + { '0': 1, '1': 3, '2': 4 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: -1 } as Aspect], + series: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(4); // two series * two metrics + + checkSiri(series[0], '0: 0', 3); + checkSiri(series[1], '0: 1', 4); + checkSiri(series[2], '1: 0', 3); + checkSiri(series[3], '1: 1', 4); + + function checkSiri(siri: Serie, label: string, y: number) { + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', label); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(3); + + siri.values.forEach(function(point: Point) { + expect(point).toHaveProperty('y', y); + }); + } + }); + + it('produces a series list in the same order as its corresponding metric column', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: -1 } as Aspect], + series: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + expect(series[0]).toHaveProperty('label', '0: 0'); + expect(series[1]).toHaveProperty('label', '0: 1'); + expect(series[2]).toHaveProperty('label', '1: 0'); + expect(series[3]).toHaveProperty('label', '1: 1'); + + // switch the order of the y columns + chart.aspects.y = chart.aspects.y.reverse(); + chart.aspects.y.forEach(function(y: any, i) { + y.i = i; + }); + + const series2 = getSeries(table, chart); + expect(series2[0]).toHaveProperty('label', '0: 1'); + expect(series2[1]).toHaveProperty('label', '0: 0'); + expect(series2[2]).toHaveProperty('label', '1: 1'); + expect(series2[3]).toHaveProperty('label', '1: 0'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts new file mode 100644 index 0000000000000..edde5b69af022 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts @@ -0,0 +1,88 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { partial } from 'lodash'; +import { getPoint } from './_get_point'; +import { addToSiri, Serie } from './_add_to_siri'; +import { Chart } from './point_series'; +import { Table } from '../../types'; + +export function getSeries(table: Table, chart: Chart) { + const aspects = chart.aspects; + const xAspect = aspects.x[0]; + const yAspect = aspects.y[0]; + const zAspect = aspects.z && aspects.z[0]; + const multiY = Array.isArray(aspects.y) && aspects.y.length > 1; + + const partGetPoint = partial(getPoint, table, xAspect, aspects.series); + + const seriesMap = new Map(); + + table.rows.forEach((row, rowIndex) => { + if (!multiY) { + const point = partGetPoint(row, rowIndex, yAspect, zAspect); + if (point) { + const id = `${point.series}-${yAspect.accessor}`; + point.seriesId = id; + addToSiri( + seriesMap, + point, + id, + point.series, + yAspect.format, + zAspect && zAspect.format, + zAspect && zAspect.title + ); + } + return; + } + + aspects.y.forEach(function(y) { + const point = partGetPoint(row, rowIndex, y, zAspect); + if (!point) { + return; + } + + // use the point's y-axis as it's series by default, + // but augment that with series aspect if it's actually + // available + let seriesId = y.accessor; + let seriesLabel = y.title; + + if (aspects.series) { + const prefix = point.series ? point.series + ': ' : ''; + seriesId = prefix + seriesId; + seriesLabel = prefix + seriesLabel; + } + + point.seriesId = seriesId; + addToSiri( + seriesMap, + point, + seriesId as string, + seriesLabel, + y.format, + zAspect && zAspect.format, + zAspect && zAspect.title + ); + }); + }); + + return [...seriesMap.values()]; +} diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts similarity index 51% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts index a8512edee658b..d3049d7675408 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts @@ -17,14 +17,22 @@ * under the License. */ -import expect from '@kbn/expect'; import moment from 'moment'; -import { initXAxis } from '../_init_x_axis'; -import { makeFakeXAspect } from '../_fake_x_aspect'; +import { initXAxis } from './_init_x_axis'; +import { makeFakeXAspect } from './_fake_x_aspect'; +import { + Aspects, + Chart, + DateHistogramOrdered, + DateHistogramParams, + HistogramOrdered, + HistogramParams, +} from './point_series'; +import { Table, Column } from '../../types'; describe('initXAxis', function() { - let chart; - let table; + let chart: Chart; + let table: Table; beforeEach(function() { chart = { @@ -32,50 +40,48 @@ describe('initXAxis', function() { x: [ { ...makeFakeXAspect(), - accessor: 0, + accessor: '0', title: 'label', }, ], - }, - }; + } as Aspects, + } as Chart; table = { - columns: [{ id: '0' }], + columns: [{ id: '0' } as Column], rows: [{ '0': 'hello' }, { '0': 'world' }, { '0': 'foo' }, { '0': 'bar' }, { '0': 'baz' }], }; }); it('sets the xAxisFormatter if the agg is not ordered', function() { initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); }); it('makes the chart ordered if the agg is ordered', function() { - chart.aspects.x[0].params.interval = 10; + (chart.aspects.x[0].params as HistogramParams).interval = 10; initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format) - .and.have.property('ordered'); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('ordered'); }); describe('xAxisOrderedValues', function() { it('sets the xAxisOrderedValues property', function() { initXAxis(chart, table); - expect(chart).to.have.property('xAxisOrderedValues'); + expect(chart).toHaveProperty('xAxisOrderedValues'); }); it('returns a list of values, preserving the table order', function() { initXAxis(chart, table); - expect(chart.xAxisOrderedValues).to.eql(['hello', 'world', 'foo', 'bar', 'baz']); + expect(chart.xAxisOrderedValues).toEqual(['hello', 'world', 'foo', 'bar', 'baz']); }); it('only returns unique values', function() { table = { - columns: [{ id: '0' }], + columns: [{ id: '0' } as Column], rows: [ { '0': 'hello' }, { '0': 'world' }, @@ -88,45 +94,46 @@ describe('initXAxis', function() { ], }; initXAxis(chart, table); - expect(chart.xAxisOrderedValues).to.eql(['hello', 'world', 'foo', 'bar', 'baz']); + expect(chart.xAxisOrderedValues).toEqual(['hello', 'world', 'foo', 'bar', 'baz']); }); it('returns the defaultValue if using fake x aspect', function() { chart = { aspects: { x: [makeFakeXAspect()], - }, - }; + } as Aspects, + } as Chart; initXAxis(chart, table); - expect(chart.xAxisOrderedValues).to.eql(['_all']); + expect(chart.xAxisOrderedValues).toEqual(['_all']); }); }); it('reads the date interval param from the x agg', function() { - chart.aspects.x[0].params.interval = 'P1D'; - chart.aspects.x[0].params.intervalESValue = 1; - chart.aspects.x[0].params.intervalESUnit = 'd'; - chart.aspects.x[0].params.date = true; + const dateHistogramParams = chart.aspects.x[0].params as DateHistogramParams; + dateHistogramParams.interval = 'P1D'; + dateHistogramParams.intervalESValue = 1; + dateHistogramParams.intervalESUnit = 'd'; + dateHistogramParams.date = true; initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format) - .and.have.property('ordered'); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('ordered'); - expect(moment.isDuration(chart.ordered.interval)).to.be(true); - expect(chart.ordered.interval.toISOString()).to.eql('P1D'); - expect(chart.ordered.intervalESValue).to.be(1); - expect(chart.ordered.intervalESUnit).to.be('d'); + expect(chart.ordered).toEqual(expect.any(Object)); + const { intervalESUnit, intervalESValue, interval } = chart.ordered as DateHistogramOrdered; + expect(moment.isDuration(interval)).toBe(true); + expect(interval.toISOString()).toEqual('P1D'); + expect(intervalESValue).toBe(1); + expect(intervalESUnit).toBe('d'); }); it('reads the numeric interval param from the x agg', function() { - chart.aspects.x[0].params.interval = 0.5; + (chart.aspects.x[0].params as HistogramParams).interval = 0.5; initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format) - .and.have.property('ordered'); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('ordered'); - expect(chart.ordered.interval).to.eql(0.5); + expect((chart.ordered as HistogramOrdered).interval).toEqual(0.5); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts similarity index 73% rename from src/legacy/ui/public/agg_response/point_series/_init_x_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts index 4a81486783b08..9d16c4857be00 100644 --- a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts @@ -19,27 +19,31 @@ import { uniq } from 'lodash'; import moment from 'moment'; +import { Chart } from './point_series'; +import { Table } from '../../types'; -export function initXAxis(chart, table) { +export function initXAxis(chart: Chart, table: Table) { const { format, title, params, accessor } = chart.aspects.x[0]; chart.xAxisOrderedValues = - accessor === -1 ? [params.defaultValue] : uniq(table.rows.map(r => r[accessor])); + accessor === -1 && 'defaultValue' in params + ? [params.defaultValue] + : uniq(table.rows.map(r => r[accessor])); chart.xAxisFormat = format; chart.xAxisLabel = title; - const { interval, date } = params; - if (interval) { - if (date) { + if ('interval' in params) { + const { interval } = params; + if ('date' in params) { const { intervalESUnit, intervalESValue } = params; chart.ordered = { interval: moment.duration(interval), - intervalESUnit: intervalESUnit, - intervalESValue: intervalESValue, + intervalESUnit, + intervalESValue, }; } else { chart.ordered = { - interval, + interval: params.interval, }; } } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts similarity index 81% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_init_y_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts index 78cd5334e6c86..df84d69c9f849 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts @@ -18,8 +18,8 @@ */ import _ from 'lodash'; -import expect from '@kbn/expect'; -import { initYAxis } from '../_init_y_axis'; +import { initYAxis } from './_init_y_axis'; +import { Chart } from './point_series'; describe('initYAxis', function() { const baseChart = { @@ -34,7 +34,7 @@ describe('initYAxis', function() { }, ], }, - }; + } as Chart; describe('with a single y aspect', function() { const singleYBaseChart = _.cloneDeep(baseChart); @@ -43,13 +43,13 @@ describe('initYAxis', function() { it('sets the yAxisFormatter the the field formats convert fn', function() { const chart = _.cloneDeep(singleYBaseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisFormat'); + expect(chart).toHaveProperty('yAxisFormat'); }); it('sets the yAxisLabel', function() { const chart = _.cloneDeep(singleYBaseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisLabel', 'y1'); + expect(chart).toHaveProperty('yAxisLabel', 'y1'); }); }); @@ -58,16 +58,15 @@ describe('initYAxis', function() { const chart = _.cloneDeep(baseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisFormat'); - expect(chart.yAxisFormat) - .to.be(chart.aspects.y[0].format) - .and.not.be(chart.aspects.y[1].format); + expect(chart).toHaveProperty('yAxisFormat'); + expect(chart.yAxisFormat).toBe(chart.aspects.y[0].format); + expect(chart.yAxisFormat).not.toBe(chart.aspects.y[1].format); }); it('does not set the yAxisLabel, it does not make sense to put multiple labels on the same axis', function() { const chart = _.cloneDeep(baseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisLabel', ''); + expect(chart).toHaveProperty('yAxisLabel', ''); }); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_init_y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts similarity index 82% rename from src/legacy/ui/public/agg_response/point_series/_init_y_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts index 42f5e79a63172..43ba0557949ac 100644 --- a/src/legacy/ui/public/agg_response/point_series/_init_y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts @@ -17,7 +17,9 @@ * under the License. */ -export function initYAxis(chart) { +import { Chart } from './point_series'; + +export function initYAxis(chart: Chart) { const y = chart.aspects.y; if (Array.isArray(y)) { @@ -28,12 +30,7 @@ export function initYAxis(chart) { const z = chart.aspects.series; if (z) { - if (Array.isArray(z)) { - chart.zAxisFormat = z[0].format; - chart.zAxisLabel = ''; - } else { - chart.zAxisFormat = z.format; - chart.zAxisLabel = z.title; - } + chart.zAxisFormat = z[0].format; + chart.zAxisLabel = ''; } } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts similarity index 76% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts index 2e08be16278d5..25e466f21c3e7 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts @@ -19,8 +19,8 @@ import moment from 'moment'; import _ from 'lodash'; -import expect from '@kbn/expect'; -import { orderedDateAxis } from '../_ordered_date_axis'; +import { orderedDateAxis } from './_ordered_date_axis'; +import { DateHistogramParams, OrderedChart } from './point_series'; describe('orderedDateAxis', function() { const baseArgs = { @@ -46,7 +46,7 @@ describe('orderedDateAxis', function() { }, ], }, - }, + } as OrderedChart, }; describe('ordered object', function() { @@ -54,24 +54,24 @@ describe('orderedDateAxis', function() { const args = _.cloneDeep(baseArgs); orderedDateAxis(args.chart); - expect(args.chart).to.have.property('ordered'); + expect(args.chart).toHaveProperty('ordered'); - expect(args.chart.ordered).to.have.property('date', true); + expect(args.chart.ordered).toHaveProperty('date', true); }); it('sets the min/max when the buckets are bounded', function() { const args = _.cloneDeep(baseArgs); orderedDateAxis(args.chart); - expect(args.chart.ordered).to.have.property('min'); - expect(args.chart.ordered).to.have.property('max'); + expect(args.chart.ordered).toHaveProperty('min'); + expect(args.chart.ordered).toHaveProperty('max'); }); it('does not set the min/max when the buckets are unbounded', function() { const args = _.cloneDeep(baseArgs); - args.chart.aspects.x[0].params.bounds = null; + (args.chart.aspects.x[0].params as DateHistogramParams).bounds = undefined; orderedDateAxis(args.chart); - expect(args.chart.ordered).to.not.have.property('min'); - expect(args.chart.ordered).to.not.have.property('max'); + expect(args.chart.ordered).not.toHaveProperty('min'); + expect(args.chart.ordered).not.toHaveProperty('max'); }); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_ordered_date_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts similarity index 72% rename from src/legacy/ui/public/agg_response/point_series/_ordered_date_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts index a1dd50dc6c71b..193b10a563563 100644 --- a/src/legacy/ui/public/agg_response/point_series/_ordered_date_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts @@ -17,17 +17,17 @@ * under the License. */ -// import moment from 'moment'; +import { OrderedChart } from './point_series'; -export function orderedDateAxis(chart) { +export function orderedDateAxis(chart: OrderedChart) { const x = chart.aspects.x[0]; - const { bounds } = x.params; + const bounds = 'bounds' in x.params ? x.params.bounds : undefined; chart.ordered.date = true; if (bounds) { - chart.ordered.min = isNaN(bounds.min) ? Date.parse(bounds.min) : bounds.min; - chart.ordered.max = isNaN(bounds.max) ? Date.parse(bounds.max) : bounds.max; + chart.ordered.min = typeof bounds.min === 'string' ? Date.parse(bounds.min) : bounds.min; + chart.ordered.max = typeof bounds.max === 'string' ? Date.parse(bounds.max) : bounds.max; } else { chart.ordered.endzones = false; } diff --git a/src/legacy/ui/public/agg_response/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts similarity index 69% rename from src/legacy/ui/public/agg_response/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts index 982c1c25a8101..9bfba4de966be 100644 --- a/src/legacy/ui/public/agg_response/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts @@ -17,12 +17,4 @@ * under the License. */ -import { buildHierarchicalData } from './hierarchical/build_hierarchical_data'; -import { buildPointSeriesData } from './point_series/point_series'; -import { search } from '../../../../plugins/data/public'; - -export const aggResponseIndex = { - hierarchical: buildHierarchicalData, - pointSeries: buildPointSeriesData, - tabify: search.tabifyAggResponse, -}; +export { buildPointSeriesData } from './point_series'; diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_main.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts similarity index 62% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_main.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts index a4c23cb537488..3725bf06660e2 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_main.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts @@ -18,17 +18,25 @@ */ import _ from 'lodash'; -import expect from '@kbn/expect'; -import { buildPointSeriesData } from '../point_series'; +import { buildPointSeriesData, Dimensions } from './point_series'; +import { Table, Column } from '../../types'; +import { setFormatService } from '../../../services'; +import { Serie } from './_add_to_siri'; describe('pointSeriesChartDataFromTable', function() { - this.slow(1000); + beforeAll(() => { + setFormatService({ + deserialize: () => ({ + convert: jest.fn(v => v), + }), + } as any); + }); it('handles a table with just a count', function() { const table = { - columns: [{ id: '0' }], + columns: [{ id: '0' } as Column], rows: [{ '0': 100 }], - }; + } as Table; const chartData = buildPointSeriesData(table, { y: [ { @@ -36,16 +44,15 @@ describe('pointSeriesChartDataFromTable', function() { params: {}, }, ], - }); + } as Dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); - expect(chartData.series).to.have.length(1); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); + expect(chartData.series).toHaveLength(1); const series = chartData.series[0]; - expect(series.values).to.have.length(1); - expect(series.values[0]) - .to.have.property('x', '_all') - .and.have.property('y', 100); + expect(series.values).toHaveLength(1); + expect(series.values[0]).toHaveProperty('x', '_all'); + expect(series.values[0]).toHaveProperty('y', 100); }); it('handles a table with x and y column', function() { @@ -59,21 +66,21 @@ describe('pointSeriesChartDataFromTable', function() { { '0': 2, '1': 200 }, { '0': 3, '1': 200 }, ], - }; + } as Table; const dimensions = { - x: [{ accessor: 0, params: {} }], + x: { accessor: 0, params: {} }, y: [{ accessor: 1, params: {} }], - }; + } as Dimensions; const chartData = buildPointSeriesData(table, dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); - expect(chartData.series).to.have.length(1); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); + expect(chartData.series).toHaveLength(1); const series = chartData.series[0]; - expect(series).to.have.property('label', 'Count'); - expect(series.values).to.have.length(3); + expect(series).toHaveProperty('label', 'Count'); + expect(series.values).toHaveLength(3); }); it('handles a table with an x and two y aspects', function() { @@ -84,23 +91,23 @@ describe('pointSeriesChartDataFromTable', function() { { '0': 2, '1': 200, '2': 300 }, { '0': 3, '1': 200, '2': 300 }, ], - }; + } as Table; const dimensions = { - x: [{ accessor: 0, params: {} }], + x: { accessor: 0, params: {} }, y: [ { accessor: 1, params: {} }, { accessor: 2, params: {} }, ], - }; + } as Dimensions; const chartData = buildPointSeriesData(table, dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); - expect(chartData.series).to.have.length(2); - chartData.series.forEach(function(siri, i) { - expect(siri).to.have.property('label', `Count-${i}`); - expect(siri.values).to.have.length(3); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); + expect(chartData.series).toHaveLength(2); + chartData.series.forEach(function(siri: Serie, i: number) { + expect(siri).toHaveProperty('label', `Count-${i}`); + expect(siri.values).toHaveLength(3); }); }); @@ -121,21 +128,21 @@ describe('pointSeriesChartDataFromTable', function() { }; const dimensions = { - x: [{ accessor: 0, params: {} }], + x: { accessor: 0, params: {} }, series: [{ accessor: 1, params: {} }], y: [ { accessor: 2, params: {} }, { accessor: 3, params: {} }, ], - }; + } as Dimensions; const chartData = buildPointSeriesData(table, dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); // one series for each extension, and then one for each metric inside - expect(chartData.series).to.have.length(4); - chartData.series.forEach(function(siri) { - expect(siri.values).to.have.length(2); + expect(chartData.series).toHaveLength(4); + chartData.series.forEach(function(siri: Serie) { + expect(siri.values).toHaveLength(2); }); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts new file mode 100644 index 0000000000000..a1681e0d71bd3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts @@ -0,0 +1,118 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Duration } from 'moment'; +import { getSeries } from './_get_series'; +import { getAspects } from './_get_aspects'; +import { initYAxis } from './_init_y_axis'; +import { initXAxis } from './_init_x_axis'; +import { orderedDateAxis } from './_ordered_date_axis'; +import { Serie } from './_add_to_siri'; +import { Column, Table } from '../../types'; + +export interface DateHistogramParams { + date: boolean; + interval: string; + intervalESValue: number; + intervalESUnit: string; + format: string; + bounds?: { + min: string | number; + max: string | number; + }; +} +export interface HistogramParams { + interval: number; +} +export interface FakeParams { + defaultValue: string; +} +export interface Dimension { + accessor: number; + format: { + id?: string; + params?: { pattern?: string; [key: string]: any }; + }; + params: DateHistogramParams | HistogramParams | FakeParams | {}; +} + +export interface Dimensions { + x: Dimension | null; + y: Dimension[]; + z?: Dimension[]; + series?: Dimension | Dimension[]; +} +export interface Aspect { + accessor: Column['id']; + column?: Dimension['accessor']; + title: Column['name']; + format: Dimension['format']; + params: Dimension['params']; +} +export type Aspects = { x: Aspect[]; y: Aspect[] } & { [key in keyof Dimensions]?: Aspect[] }; + +export interface DateHistogramOrdered { + interval: Duration; + intervalESUnit: DateHistogramParams['intervalESUnit']; + intervalESValue: DateHistogramParams['intervalESValue']; +} +export interface HistogramOrdered { + interval: HistogramParams['interval']; +} + +type Ordered = (DateHistogramOrdered | HistogramOrdered) & { + date?: boolean; + min?: number; + max?: number; + endzones?: boolean; +}; + +export interface Chart { + aspects: Aspects; + series: Serie[]; + xAxisOrderedValues?: Array; + xAxisFormat?: Dimension['format']; + xAxisLabel?: Column['name']; + yAxisFormat?: Dimension['format']; + yAxisLabel?: Column['name']; + zAxisFormat?: Dimension['format']; + zAxisLabel?: Column['name']; + ordered?: Ordered; +} + +export type OrderedChart = Chart & { ordered: Ordered }; + +export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { + const chart = { + aspects: getAspects(table, dimensions), + } as Chart; + + initXAxis(chart, table); + initYAxis(chart); + + if ('date' in chart.aspects.x[0].params) { + // initXAxis will turn `chart` into an `OrderedChart if it is a date axis` + orderedDateAxis(chart as OrderedChart); + } + + chart.series = getSeries(table, chart); + + delete chart.aspects; + return chart; +}; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js index 9ba86c5181a4c..b5f80303b1d74 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js @@ -17,8 +17,8 @@ * under the License. */ -import { buildHierarchicalData, buildPointSeriesData } from '../legacy_imports'; import { getFormatService } from '../services'; +import { buildHierarchicalData, buildPointSeriesData } from './helpers'; function tableResponseHandler(table, dimensions) { const converted = { tables: [] }; @@ -72,7 +72,7 @@ function tableResponseHandler(table, dimensions) { function convertTableGroup(tableGroup, convertTable) { const tables = tableGroup.tables; - if (!tables.length) return; + if (!tables || !tables.length) return; const firstChild = tables[0]; if (firstChild.columns) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts new file mode 100644 index 0000000000000..4a8bebc493235 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts @@ -0,0 +1,130 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { setFormatService } from '../services'; + +jest.mock('./helpers', () => ({ + buildHierarchicalData: jest.fn(() => ({})), + buildPointSeriesData: jest.fn(() => ({})), +})); + +// @ts-ignore +import { vislibSeriesResponseHandler, vislibSlicesResponseHandler } from './response_handler'; +import { buildHierarchicalData, buildPointSeriesData } from './helpers'; +import { Table } from './types'; + +describe('response_handler', () => { + describe('vislibSlicesResponseHandler', () => { + test('should not call buildHierarchicalData when no columns', () => { + vislibSlicesResponseHandler({ rows: [] }, {}); + expect(buildHierarchicalData).not.toHaveBeenCalled(); + }); + + test('should call buildHierarchicalData', () => { + const response = { + rows: [{ 'col-0-1': 1 }], + columns: [{ id: 'col-0-1', name: 'Count' }], + }; + const dimensions = { metric: { accessor: 0 } }; + vislibSlicesResponseHandler(response, dimensions); + + expect(buildHierarchicalData).toHaveBeenCalledWith( + { columns: [...response.columns], rows: [...response.rows] }, + dimensions + ); + }); + }); + + describe('vislibSeriesResponseHandler', () => { + let resp: Table; + let expected: any; + + beforeAll(() => { + setFormatService({ + deserialize: () => ({ + convert: jest.fn(v => v), + }), + } as any); + }); + + beforeAll(() => { + resp = { + rows: [ + { 'col-0-3': 158599872, 'col-1-1': 1 }, + { 'col-0-3': 158599893, 'col-1-1': 2 }, + { 'col-0-3': 158599908, 'col-1-1': 1 }, + ], + columns: [ + { id: 'col-0-3', name: 'timestamp per 30 seconds' }, + { id: 'col-1-1', name: 'Count' }, + ], + } as Table; + + const colId = resp.columns[0].id; + expected = [ + { label: `${resp.rows[0][colId]}: ${resp.columns[0].name}` }, + { label: `${resp.rows[1][colId]}: ${resp.columns[0].name}` }, + { label: `${resp.rows[2][colId]}: ${resp.columns[0].name}` }, + ]; + }); + + test('should not call buildPointSeriesData when no columns', () => { + vislibSeriesResponseHandler({ rows: [] }, {}); + expect(buildPointSeriesData).not.toHaveBeenCalled(); + }); + + test('should call buildPointSeriesData', () => { + const response = { + rows: [{ 'col-0-1': 1 }], + columns: [{ id: 'col-0-1', name: 'Count' }], + }; + const dimensions = { x: null, y: { accessor: 0 } }; + vislibSeriesResponseHandler(response, dimensions); + + expect(buildPointSeriesData).toHaveBeenCalledWith( + { columns: [...response.columns], rows: [...response.rows] }, + dimensions + ); + }); + + test('should split columns', () => { + const dimensions = { + x: null, + y: [{ accessor: 1 }], + splitColumn: [{ accessor: 0 }], + }; + + const convertedResp = vislibSlicesResponseHandler(resp, dimensions); + expect(convertedResp.columns).toHaveLength(resp.rows.length); + expect(convertedResp.columns).toEqual(expected); + }); + + test('should split rows', () => { + const dimensions = { + x: null, + y: [{ accessor: 1 }], + splitRow: [{ accessor: 0 }], + }; + + const convertedResp = vislibSlicesResponseHandler(resp, dimensions); + expect(convertedResp.rows).toHaveLength(resp.rows.length); + expect(convertedResp.rows).toEqual(expected); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_response/point_series/_add_to_siri.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/types.ts similarity index 67% rename from src/legacy/ui/public/agg_response/point_series/_add_to_siri.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/types.ts index 9a0fcbc7b267c..ad59603663b84 100644 --- a/src/legacy/ui/public/agg_response/point_series/_add_to_siri.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/types.ts @@ -17,22 +17,26 @@ * under the License. */ -export function addToSiri(series, point, id, yLabel, yFormat, zFormat, zLabel) { - id = id == null ? '' : id + ''; +export interface Column { + // -1 value can be in a fake X aspect + id: string | -1; + name: string; +} - if (series.has(id)) { - series.get(id).values.push(point); - return; - } +export interface Row { + [key: string]: number | string | object; +} - series.set(id, { - id: id.split('-').pop(), - rawId: id, - label: yLabel == null ? id : yLabel, - count: 0, - values: [point], - format: yFormat, - zLabel, - zFormat, - }); +export interface TableParent { + table: Table; + tables?: Table[]; + column: number; + row: number; + key: number; + name: string; +} +export interface Table { + columns: Column[]; + rows: Row[]; + $parent?: TableParent; } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js deleted file mode 100644 index 43a10ebbfb12e..0000000000000 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { addToSiri } from '../_add_to_siri'; - -describe('addToSiri', function() { - it('creates a new series the first time it sees an id', function() { - const series = new Map(); - const point = {}; - const id = 'id'; - addToSiri(series, point, id, id, { id: id }); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(id); - expect(series.get(id).values).to.have.length(1); - expect(series.get(id).values[0]).to.be(point); - }); - - it('adds points to existing series if id has been seen', function() { - const series = new Map(); - const id = 'id'; - - const point = {}; - addToSiri(series, point, id, id, { id: id }); - - const point2 = {}; - addToSiri(series, point2, id, id, { id: id }); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(id); - expect(series.get(id).values).to.have.length(2); - expect(series.get(id).values[0]).to.be(point); - expect(series.get(id).values[1]).to.be(point2); - }); - - it('allows overriding the series label', function() { - const series = new Map(); - const id = 'id'; - const label = 'label'; - const point = {}; - addToSiri(series, point, id, label, { id: id }); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(label); - expect(series.get(id).values).to.have.length(1); - expect(series.get(id).values[0]).to.be(point); - }); - - it('correctly sets id and rawId', function() { - const series = new Map(); - const id = 'id-id2'; - - const point = {}; - addToSiri(series, point, id); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(id); - expect(series.get(id).rawId).to.be(id); - expect(series.get(id).id).to.be('id2'); - }); -}); diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js deleted file mode 100644 index 0eb2c608d6d6c..0000000000000 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { getPoint } from '../_get_point'; - -describe('getPoint', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 4, '1': 'NaN', '2': 6 }, - ], - }; - - describe('Without series aspect', function() { - let seriesAspect; - let xAspect; - let yAspect; - let yScale; - - beforeEach(function() { - seriesAspect = null; - xAspect = { accessor: 0 }; - yAspect = { accessor: 1, title: 'Y' }; - yScale = 5; - }); - - it('properly unwraps and scales values', function() { - const row = table.rows[0]; - const zAspect = { accessor: 2 }; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect, zAspect); - - expect(point) - .to.have.property('x', 1) - .and.have.property('y', 10) - .and.have.property('z', 3) - .and.have.property('series', yAspect.title); - }); - - it('ignores points with a y value of NaN', function() { - const row = table.rows[1]; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 1, yAspect); - expect(point).to.be(void 0); - }); - }); - - describe('With series aspect', function() { - let row; - let xAspect; - let yAspect; - let yScale; - - beforeEach(function() { - row = table.rows[0]; - xAspect = { accessor: 0 }; - yAspect = { accessor: 2 }; - yScale = null; - }); - - it('properly unwraps and scales values', function() { - const seriesAspect = [{ accessor: 1 }]; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect); - - expect(point) - .to.have.property('x', 1) - .and.have.property('series', '2') - .and.have.property('y', 3); - }); - - it('properly formats series values', function() { - const seriesAspect = [{ accessor: 1, format: { id: 'number', params: { pattern: '$' } } }]; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect); - - expect(point) - .to.have.property('x', 1) - .and.have.property('series', '$2') - .and.have.property('y', 3); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js deleted file mode 100644 index 1727994976383..0000000000000 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { getSeries } from '../_get_series'; - -describe('getSeries', function() { - it('produces a single series with points for each row', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: 0 }], - y: [{ accessor: 1, title: 'y' }], - z: { accessor: 2 }, - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(1); - - const siri = series[0]; - expect(siri) - .to.be.an('object') - .and.have.property('label', chart.aspects.y.title) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(5); - - siri.values.forEach(function(point) { - expect(point) - .to.have.property('x', 1) - .and.property('y', 2) - .and.property('z', 3); - }); - }); - - it('adds the seriesId to each point', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: 0 }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - - series[0].values.forEach(function(point) { - expect(point).to.have.property('seriesId', 1); - }); - - series[1].values.forEach(function(point) { - expect(point).to.have.property('seriesId', 2); - }); - }); - - it('produces multiple series if there are multiple y aspects', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: 0 }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(2); - - series.forEach(function(siri, i) { - expect(siri) - .to.be.an('object') - .and.have.property('label', '' + i) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(5); - - siri.values.forEach(function(point) { - expect(point) - .to.have.property('x', 1) - .and.property('y', i + 2); - }); - }); - }); - - it('produces multiple series if there is a series aspect', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: -1 }], - series: [{ accessor: 0, fieldFormatter: _.identity }], - y: [{ accessor: 1, title: '0' }], - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(2); - - series.forEach(function(siri, i) { - expect(siri) - .to.be.an('object') - .and.have.property('label', '' + i) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(3); - - siri.values.forEach(function(point) { - expect(point).to.have.property('y', 2); - }); - }); - }); - - it('produces multiple series if there is a series aspect and multiple y aspects', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 0, '1': 3, '2': 4 }, - { '0': 1, '1': 3, '2': 4 }, - { '0': 0, '1': 3, '2': 4 }, - { '0': 1, '1': 3, '2': 4 }, - { '0': 0, '1': 3, '2': 4 }, - { '0': 1, '1': 3, '2': 4 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: -1 }], - series: [{ accessor: 0, fieldFormatter: _.identity }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(4); // two series * two metrics - - checkSiri(series[0], '0: 0', 3); - checkSiri(series[1], '0: 1', 4); - checkSiri(series[2], '1: 0', 3); - checkSiri(series[3], '1: 1', 4); - - function checkSiri(siri, label, y) { - expect(siri) - .to.be.an('object') - .and.have.property('label', label) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(3); - - siri.values.forEach(function(point) { - expect(point).to.have.property('y', y); - }); - } - }); - - it('produces a series list in the same order as its corresponding metric column', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: -1 }], - series: [{ accessor: 0, fieldFormatter: _.identity }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - expect(series[0]).to.have.property('label', '0: 0'); - expect(series[1]).to.have.property('label', '0: 1'); - expect(series[2]).to.have.property('label', '1: 0'); - expect(series[3]).to.have.property('label', '1: 1'); - - // switch the order of the y columns - chart.aspects.y = chart.aspects.y.reverse(); - chart.aspects.y.forEach(function(y, i) { - y.i = i; - }); - - const series2 = getSeries(table, chart); - expect(series2[0]).to.have.property('label', '0: 1'); - expect(series2[1]).to.have.property('label', '0: 0'); - expect(series2[2]).to.have.property('label', '1: 1'); - expect(series2[3]).to.have.property('label', '1: 0'); - }); -}); diff --git a/src/legacy/ui/public/agg_response/point_series/_get_series.js b/src/legacy/ui/public/agg_response/point_series/_get_series.js deleted file mode 100644 index 73c1735191abc..0000000000000 --- a/src/legacy/ui/public/agg_response/point_series/_get_series.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { getPoint } from './_get_point'; -import { addToSiri } from './_add_to_siri'; - -export function getSeries(table, chart) { - const aspects = chart.aspects; - const xAspect = aspects.x[0]; - const yAspect = aspects.y[0]; - const zAspect = aspects.z && aspects.z.length ? aspects.z[0] : aspects.z; - const multiY = Array.isArray(aspects.y) && aspects.y.length > 1; - const yScale = chart.yScale; - - const partGetPoint = _.partial(getPoint, table, xAspect, aspects.series, yScale); - - let series = _(table.rows) - .transform(function(series, row, rowIndex) { - if (!multiY) { - const point = partGetPoint(row, rowIndex, yAspect, zAspect); - if (point) { - const id = `${point.series}-${yAspect.accessor}`; - point.seriesId = id; - addToSiri( - series, - point, - id, - point.series, - yAspect.format, - zAspect && zAspect.format, - zAspect && zAspect.title - ); - } - return; - } - - aspects.y.forEach(function(y) { - const point = partGetPoint(row, rowIndex, y, zAspect); - if (!point) return; - - // use the point's y-axis as it's series by default, - // but augment that with series aspect if it's actually - // available - let seriesId = y.accessor; - let seriesLabel = y.title; - - if (aspects.series) { - const prefix = point.series ? point.series + ': ' : ''; - seriesId = prefix + seriesId; - seriesLabel = prefix + seriesLabel; - } - - point.seriesId = seriesId; - addToSiri( - series, - point, - seriesId, - seriesLabel, - y.format, - zAspect && zAspect.format, - zAspect && zAspect.title - ); - }); - }, new Map()) - .thru(series => [...series.values()]) - .value(); - - if (multiY) { - series = _.sortBy(series, function(siri) { - const firstVal = siri.values[0]; - let y; - - if (firstVal) { - y = _.find(aspects.y, function(y) { - return y.accessor === firstVal.accessor; - }); - } - - return y ? y.i : series.length; - }); - } - return series; -} diff --git a/src/legacy/ui/public/agg_response/point_series/point_series.js b/src/legacy/ui/public/agg_response/point_series/point_series.js deleted file mode 100644 index 8489f7bc2ca45..0000000000000 --- a/src/legacy/ui/public/agg_response/point_series/point_series.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getSeries } from './_get_series'; -import { getAspects } from './_get_aspects'; -import { initYAxis } from './_init_y_axis'; -import { initXAxis } from './_init_x_axis'; -import { orderedDateAxis } from './_ordered_date_axis'; - -export const buildPointSeriesData = (table, dimensions) => { - const chart = { - aspects: getAspects(table, dimensions), - }; - - initXAxis(chart, table); - initYAxis(chart); - - if (chart.aspects.x[0].params.date) { - orderedDateAxis(chart); - } - - chart.series = getSeries(table, chart); - - delete chart.aspects; - return chart; -}; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 905c88a6d18a0..532c49803e7b0 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -27,7 +27,6 @@ import 'uiExports/search'; import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; -import 'ui/agg_response'; import 'leaflet'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e63e1c8ad2c91..68997bd713185 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -130,7 +130,6 @@ "charts.colormaps.greysText": "グレー", "charts.colormaps.redsText": "赤", "charts.colormaps.yellowToRedText": "黄色から赤", - "common.ui.aggResponse.allDocsTitle": "すべてのドキュメント", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "エラー", "common.ui.errorAutoCreateIndex.errorDescription": "Elasticsearch クラスターの {autoCreateIndexActionConfig} 設定が原因で、Kibana が保存されたオブジェクトを格納するインデックスを自動的に作成できないようです。Kibana は、保存されたオブジェクトインデックスが適切なマッピング/スキーマを使用し Kibana から Elasticsearch へのポーリングの回数を減らすための最適な手段であるため、この Elasticsearch の機能を使用します。", "common.ui.errorAutoCreateIndex.errorDisclaimer": "申し訳ございませんが、この問題が解決されるまで Kibana で何も保存することができません。", @@ -3828,6 +3827,7 @@ "visTypeVega.visualization.renderErrorTitle": "Vega エラー", "visTypeVega.visualization.unableToFindDefaultIndexErrorMessage": "デフォルトのインデックスが見つかりません", "visTypeVega.visualization.unableToRenderWithoutDataWarningMessage": "データなしにはレンダリングできません", + "visTypeVislib.aggResponse.allDocsTitle": "すべてのドキュメント", "visTypeVislib.area.areaDescription": "折れ線グラフの下の数量を強調します。", "visTypeVislib.area.areaTitle": "エリア", "visTypeVislib.area.countText": "カウント", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cc75ceb988d97..fec2b48383606 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -130,7 +130,6 @@ "charts.colormaps.greysText": "灰色", "charts.colormaps.redsText": "红色", "charts.colormaps.yellowToRedText": "黄到红", - "common.ui.aggResponse.allDocsTitle": "所有文档", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "错误", "common.ui.errorAutoCreateIndex.errorDescription": "似乎 Elasticsearch 集群的 {autoCreateIndexActionConfig} 设置使 Kibana 无法自动创建用于存储已保存对象的索引。Kibana 将使用此 Elasticsearch 功能,因为这是确保已保存对象索引使用正确映射/架构的最好方式,而且其允许 Kibana 较少地轮询 Elasticsearch。", "common.ui.errorAutoCreateIndex.errorDisclaimer": "但是,只有解决了此问题后,您才能在 Kibana 保存内容。", @@ -3829,6 +3828,7 @@ "visTypeVega.visualization.renderErrorTitle": "Vega 错误", "visTypeVega.visualization.unableToFindDefaultIndexErrorMessage": "找不到默认索引", "visTypeVega.visualization.unableToRenderWithoutDataWarningMessage": "没有数据时无法渲染", + "visTypeVislib.aggResponse.allDocsTitle": "所有文档", "visTypeVislib.area.areaDescription": "突出折线图下方的数量", "visTypeVislib.area.areaTitle": "面积图", "visTypeVislib.area.countText": "计数",