mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Add visualization renderer to support Kibana visualizations inside Canvas.
This commit is contained in:
parent
2574028c7d
commit
b66375eb8f
61 changed files with 605 additions and 417 deletions
|
@ -30,6 +30,7 @@ import { shape } from './shape';
|
|||
import { string } from './string';
|
||||
import { style } from './style';
|
||||
import { kibanaContext } from './kibana_context';
|
||||
import { kibanaDatatable } from './kibana_datatable';
|
||||
|
||||
export const typeSpecs = [
|
||||
boolean,
|
||||
|
@ -45,4 +46,5 @@ export const typeSpecs = [
|
|||
string,
|
||||
style,
|
||||
kibanaContext,
|
||||
kibanaDatatable,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { map } from 'lodash';
|
||||
|
||||
export const kibanaDatatable = () => ({
|
||||
name: 'kibana_datatable',
|
||||
from: {
|
||||
datatable: context => {
|
||||
context.columns.forEach(c => c.id = c.name);
|
||||
return {
|
||||
type: 'kibana_datatable',
|
||||
rows: context.rows,
|
||||
columns: context.columns,
|
||||
};
|
||||
},
|
||||
pointseries: context => {
|
||||
const columns = map(context.columns, (column, name) => {
|
||||
return { id: name, name, ...column };
|
||||
});
|
||||
return {
|
||||
type: 'kibana_datatable',
|
||||
rows: context.rows,
|
||||
columns: columns,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
|
@ -34,7 +34,7 @@ const courierRequestHandler = courierRequestHandlerProvider().handler;
|
|||
|
||||
export const esaggs = () => ({
|
||||
name: 'esaggs',
|
||||
type: 'datatable',
|
||||
type: 'kibana_datatable',
|
||||
context: {
|
||||
types: [
|
||||
'kibana_context',
|
||||
|
@ -90,7 +90,7 @@ export const esaggs = () => ({
|
|||
});
|
||||
|
||||
return {
|
||||
type: 'datatable',
|
||||
type: 'kibana_datatable',
|
||||
rows: response.rows,
|
||||
columns: response.columns.map(column => ({
|
||||
id: column.id,
|
||||
|
|
|
@ -40,10 +40,8 @@ export const inputControlVis = () => ({
|
|||
type: 'render',
|
||||
as: 'visualization',
|
||||
value: {
|
||||
visConfig: {
|
||||
type: 'input_controls_vis',
|
||||
params: params
|
||||
},
|
||||
visType: 'input_control_vis',
|
||||
visConfig: params
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,12 +46,10 @@ export const kibanaMarkdown = () => ({
|
|||
type: 'render',
|
||||
as: 'visualization',
|
||||
value: {
|
||||
visType: 'markdown',
|
||||
visConfig: {
|
||||
type: 'markdown',
|
||||
params: {
|
||||
markdown: args.spec,
|
||||
...params,
|
||||
}
|
||||
markdown: args.spec,
|
||||
...params,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ export const metric = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.metric.help', {
|
||||
|
@ -44,10 +44,8 @@ export const metric = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig: {
|
||||
type: 'metric',
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: 'metric',
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { VislibSlicesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
|
||||
import chrome from 'ui/chrome';
|
||||
import { VislibSlicesResponseHandlerProvider as vislibSlicesResponseHandler } from 'ui/vis/response_handlers/vislib';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const kibanaPie = () => ({
|
||||
|
@ -26,7 +25,7 @@ export const kibanaPie = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.pie.help', {
|
||||
|
@ -39,9 +38,7 @@ export const kibanaPie = () => ({
|
|||
},
|
||||
},
|
||||
async fn(context, args) {
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const Private = $injector.get('Private');
|
||||
const responseHandler = Private(VislibSlicesResponseHandlerProvider).handler;
|
||||
const responseHandler = vislibSlicesResponseHandler().handler;
|
||||
const visConfigParams = JSON.parse(args.visConfig);
|
||||
|
||||
const convertedData = await responseHandler(context, visConfigParams.dimensions);
|
||||
|
@ -51,10 +48,8 @@ export const kibanaPie = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: convertedData,
|
||||
visConfig: {
|
||||
type: args.type,
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: 'pie',
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export const regionmap = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.regionmap.help', {
|
||||
|
@ -44,10 +44,8 @@ export const regionmap = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig: {
|
||||
type: 'region_map',
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: 'region_map',
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const kibanaTable = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.table.help', {
|
||||
|
@ -50,10 +50,8 @@ export const kibanaTable = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: convertedData,
|
||||
visConfig: {
|
||||
type: 'table',
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: 'table',
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export const tagcloud = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.tagcloud.help', {
|
||||
|
@ -44,10 +44,8 @@ export const tagcloud = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig: {
|
||||
type: 'tag_cloud',
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: 'tagcloud',
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export const tilemap = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.tilemap.help', {
|
||||
|
@ -51,10 +51,8 @@ export const tilemap = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: convertedData,
|
||||
visConfig: {
|
||||
type: 'tile_map',
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: 'tile_map',
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -52,18 +52,26 @@ export const timelionVis = () => ({
|
|||
const Private = $injector.get('Private');
|
||||
const timelionRequestHandler = Private(TimelionRequestHandlerProvider).handler;
|
||||
|
||||
const visParams = { expression: args.expression, interval: args.interval };
|
||||
|
||||
const response = await timelionRequestHandler({
|
||||
timeRange: get(context, 'timeRange', null),
|
||||
query: get(context, 'query', null),
|
||||
filters: get(context, 'filters', null),
|
||||
forceFetch: true,
|
||||
visParams: { expression: args.expression, interval: args.interval }
|
||||
visParams: visParams,
|
||||
});
|
||||
|
||||
response.visType = 'timelion';
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'visualization',
|
||||
value: response,
|
||||
value: {
|
||||
visParams,
|
||||
visType: 'timelion',
|
||||
visData: response,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -64,10 +64,17 @@ export const tsvb = () => ({
|
|||
uiState: uiState,
|
||||
});
|
||||
|
||||
response.visType = 'metrics';
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'visualization',
|
||||
value: response,
|
||||
value: {
|
||||
visType: 'metrics',
|
||||
visConfig: params,
|
||||
uiState: uiState,
|
||||
visData: response,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -58,11 +58,9 @@ export const vega = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: response,
|
||||
visType: 'vega',
|
||||
visConfig: {
|
||||
type: 'vega',
|
||||
params: {
|
||||
spec: args.spec
|
||||
}
|
||||
spec: args.spec
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ export const vislib = () => ({
|
|||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'datatable'
|
||||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.vislib.help', {
|
||||
|
@ -51,10 +51,8 @@ export const vislib = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: convertedData,
|
||||
visConfig: {
|
||||
type: args.type,
|
||||
params: visConfigParams,
|
||||
},
|
||||
visType: args.type,
|
||||
visConfig: visConfigParams,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
}
|
||||
|
|
|
@ -134,10 +134,8 @@ export const visualization = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig: {
|
||||
type: args.type,
|
||||
params: visConfigParams
|
||||
},
|
||||
visType: args.type,
|
||||
visConfig: visConfigParams
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { functions } from './functions';
|
|||
import { functionsRegistry } from './functions_registry';
|
||||
import { typesRegistry } from './types_registry';
|
||||
import { renderFunctionsRegistry } from './render_functions_registry';
|
||||
import { visualization } from './renderers/visualization';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
|
@ -38,6 +39,7 @@ function addFunction(fnDef) {
|
|||
}
|
||||
|
||||
functions.forEach(addFunction);
|
||||
renderFunctionsRegistry.register(visualization);
|
||||
|
||||
let _resolve;
|
||||
let _interpreterPromise;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 chrome from 'ui/chrome';
|
||||
import { visualizationLoader } from 'ui/visualize/loader/visualization_loader';
|
||||
import { VisProvider } from 'ui/visualize/loader/vis';
|
||||
|
||||
export const visualization = () => ({
|
||||
name: 'visualization',
|
||||
displayName: 'visualization',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, config, handlers) => {
|
||||
const { visData, visConfig, params } = config;
|
||||
const visType = config.visType || visConfig.type;
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const Private = $injector.get('Private');
|
||||
const Vis = Private(VisProvider);
|
||||
|
||||
if (handlers.vis) {
|
||||
// special case in visualize, we need to render first (without executing the expression), for maps to work
|
||||
if (visConfig) {
|
||||
handlers.vis.setCurrentState({ type: visType, params: visConfig });
|
||||
}
|
||||
} else {
|
||||
handlers.vis = new Vis({
|
||||
type: visType,
|
||||
params: visConfig,
|
||||
});
|
||||
handlers.vis.eventsSubject = handlers.eventsSubject;
|
||||
}
|
||||
|
||||
const uiState = handlers.uiState || handlers.vis.getUiState();
|
||||
|
||||
handlers.onDestroy(() => visualizationLoader.destroy());
|
||||
|
||||
await visualizationLoader.render(domNode, handlers.vis, visData, uiState, params).then(() => {
|
||||
if (handlers.done) handlers.done();
|
||||
});
|
||||
},
|
||||
});
|
|
@ -763,7 +763,15 @@ function discoverController(
|
|||
Promise
|
||||
.resolve(responseHandler(tabifiedData, buildVislibDimensions($scope.vis, $scope.timeRange)))
|
||||
.then(resp => {
|
||||
visualizeHandler.render({ value: resp });
|
||||
visualizeHandler.render({
|
||||
as: 'visualization',
|
||||
value: {
|
||||
visType: 'histogram',
|
||||
visData: resp,
|
||||
visConfig: $scope.vis.params,
|
||||
params: {},
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -41,18 +41,10 @@ describe('TagCloudVisualizationTest', function () {
|
|||
const dummyTableGroup = {
|
||||
columns: [{
|
||||
id: 'col-0',
|
||||
'aggConfig': {
|
||||
'id': '2',
|
||||
'enabled': true,
|
||||
'type': 'terms',
|
||||
'schema': 'segment',
|
||||
'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' },
|
||||
fieldFormatter: () => (x => x)
|
||||
}, 'title': 'geo.dest: Descending'
|
||||
title: 'geo.dest: Descending'
|
||||
}, {
|
||||
id: 'col-1',
|
||||
'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} },
|
||||
'title': 'Count'
|
||||
title: 'Count'
|
||||
}],
|
||||
rows: [
|
||||
{ 'col-0': 'CN', 'col-1': 26 },
|
||||
|
@ -76,7 +68,11 @@ describe('TagCloudVisualizationTest', function () {
|
|||
setupDOM('512px', '512px');
|
||||
imageComparator = new ImageComparator();
|
||||
vis = new Vis(indexPattern, {
|
||||
type: 'tagcloud'
|
||||
type: 'tagcloud',
|
||||
params: {
|
||||
bucket: { accessor: 0, format: {} },
|
||||
metric: { accessor: 0, format: {} },
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -73,7 +73,7 @@ export class TagCloudVisualization {
|
|||
this._updateParams();
|
||||
}
|
||||
|
||||
if (status.data) {
|
||||
if (status.data || status.params) {
|
||||
this._updateData(data);
|
||||
}
|
||||
|
||||
|
@ -115,10 +115,11 @@ export class TagCloudVisualization {
|
|||
return;
|
||||
}
|
||||
|
||||
const bucketFormatter = this._vis.params.bucket ? getFormat(this._vis.params.bucket.format) : null;
|
||||
const hasTags = data.columns.length === 2;
|
||||
const tagColumn = hasTags ? data.columns[0].id : -1;
|
||||
const metricColumn = data.columns[hasTags ? 1 : 0].id;
|
||||
const bucket = this._vis.params.bucket;
|
||||
const metric = this._vis.params.metric;
|
||||
const bucketFormatter = bucket ? getFormat(bucket.format) : null;
|
||||
const tagColumn = bucket ? data.columns[bucket.accessor].id : -1;
|
||||
const metricColumn = data.columns[metric.accessor].id;
|
||||
const tags = data.rows.map((row, rowIndex) => {
|
||||
const tag = row[tagColumn] === undefined ? 'all' : row[tagColumn];
|
||||
const metric = row[metricColumn];
|
||||
|
|
|
@ -1,87 +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 { toArray } from 'lodash';
|
||||
import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
||||
|
||||
export function convertTableProvider(tooltipFormatter) {
|
||||
return function (table, { metric, buckets = [] }) {
|
||||
let slices;
|
||||
const names = {};
|
||||
const metricColumn = table.columns[metric.accessor];
|
||||
const metricFieldFormatter = getFormat(metric.format);
|
||||
|
||||
if (!buckets.length) {
|
||||
slices = [{
|
||||
name: metricColumn.name,
|
||||
size: table.rows[0][metricColumn.id],
|
||||
aggConfig: metricColumn.aggConfig
|
||||
}];
|
||||
names[metricColumn.name] = metricColumn.name;
|
||||
} else {
|
||||
slices = [];
|
||||
table.rows.forEach((row, rowIndex) => {
|
||||
let parent;
|
||||
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 name = bucketFormatter.convert(row[bucketColumn.id]);
|
||||
const size = row[bucketValueColumn.id];
|
||||
names[name] = name;
|
||||
|
||||
let slice = dataLevel.find(slice => slice.name === name);
|
||||
if (!slice) {
|
||||
slice = {
|
||||
name,
|
||||
size,
|
||||
parent,
|
||||
children: [],
|
||||
aggConfig: bucketColumn.aggConfig,
|
||||
rawData: {
|
||||
table,
|
||||
row: rowIndex,
|
||||
column: bucket.accessor,
|
||||
value: row[bucketColumn.id],
|
||||
},
|
||||
};
|
||||
dataLevel.push(slice);
|
||||
}
|
||||
|
||||
parent = slice;
|
||||
dataLevel = slice.children;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hits: table.rows.length,
|
||||
raw: table,
|
||||
names: toArray(names),
|
||||
tooltipFormatter: tooltipFormatter(metricFieldFormatter),
|
||||
slices: {
|
||||
children: [
|
||||
...slices
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
|
@ -17,10 +17,69 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { HierarchicalTooltipFormatterProvider } from './_hierarchical_tooltip_formatter';
|
||||
import { convertTableProvider } from './_convert_table';
|
||||
import { toArray } from 'lodash';
|
||||
import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
||||
|
||||
export function BuildHierarchicalDataProvider(Private) {
|
||||
const tooltipFormatter = Private(HierarchicalTooltipFormatterProvider);
|
||||
return convertTableProvider(tooltipFormatter);
|
||||
}
|
||||
export const buildHierarchicalData = (table, { metric, buckets = [] }) => {
|
||||
let slices;
|
||||
const names = {};
|
||||
const metricColumn = table.columns[metric.accessor];
|
||||
const metricFieldFormatter = metric.format;
|
||||
|
||||
if (!buckets.length) {
|
||||
slices = [{
|
||||
name: metricColumn.name,
|
||||
size: table.rows[0][metricColumn.id],
|
||||
aggConfig: metricColumn.aggConfig
|
||||
}];
|
||||
names[metricColumn.name] = metricColumn.name;
|
||||
} else {
|
||||
slices = [];
|
||||
table.rows.forEach((row, rowIndex) => {
|
||||
let parent;
|
||||
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 name = bucketFormatter.convert(row[bucketColumn.id]);
|
||||
const size = row[bucketValueColumn.id];
|
||||
names[name] = name;
|
||||
|
||||
let slice = dataLevel.find(slice => slice.name === name);
|
||||
if (!slice) {
|
||||
slice = {
|
||||
name,
|
||||
size,
|
||||
parent,
|
||||
children: [],
|
||||
aggConfig: bucketColumn.aggConfig,
|
||||
rawData: {
|
||||
table,
|
||||
row: rowIndex,
|
||||
column: bucket.accessor,
|
||||
value: row[bucketColumn.id],
|
||||
},
|
||||
};
|
||||
dataLevel.push(slice);
|
||||
}
|
||||
|
||||
parent = slice;
|
||||
dataLevel = slice.children;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hits: table.rows.length,
|
||||
raw: table,
|
||||
names: toArray(names),
|
||||
tooltipFormatter: metricFieldFormatter,
|
||||
slices: {
|
||||
children: [
|
||||
...slices
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { convertTableProvider } from './_convert_table';
|
||||
import { buildHierarchicalData } from './build_hierarchical_data';
|
||||
import { LegacyResponseHandlerProvider as legacyResponseHandlerProvider } from '../../vis/response_handlers/legacy';
|
||||
|
||||
jest.mock('../../registry/field_formats', () => ({
|
||||
|
@ -34,8 +34,6 @@ jest.mock('../../chrome', () => ({
|
|||
}));
|
||||
|
||||
describe('buildHierarchicalData convertTable', () => {
|
||||
const mockToolTipFormatter = () => ({});
|
||||
const convertTable = convertTableProvider(mockToolTipFormatter);
|
||||
const responseHandler = legacyResponseHandlerProvider().handler;
|
||||
|
||||
describe('metric only', () => {
|
||||
|
@ -60,7 +58,7 @@ describe('buildHierarchicalData convertTable', () => {
|
|||
});
|
||||
|
||||
it('should set the slices with one child to a consistent label', () => {
|
||||
const results = convertTable(table, dimensions);
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
const checkLabel = 'Average bytes';
|
||||
expect(results).toHaveProperty('names');
|
||||
expect(results.names).toEqual([checkLabel]);
|
||||
|
@ -120,40 +118,40 @@ describe('buildHierarchicalData convertTable', () => {
|
|||
|
||||
it('should set the correct hits attribute for each of the results', () => {
|
||||
tables.forEach(t => {
|
||||
const results = convertTable(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 = convertTable(tables[0].tables[0], dimensions);
|
||||
const results0 = buildHierarchicalData(tables[0].tables[0], dimensions);
|
||||
expect(results0).toHaveProperty('names');
|
||||
expect(results0.names).toHaveLength(5);
|
||||
|
||||
const results1 = convertTable(tables[1].tables[0], dimensions);
|
||||
const results1 = buildHierarchicalData(tables[1].tables[0], dimensions);
|
||||
expect(results1).toHaveProperty('names');
|
||||
expect(results1.names).toHaveLength(5);
|
||||
|
||||
const results2 = convertTable(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 = convertTable(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');
|
||||
|
||||
const results1 = convertTable(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');
|
||||
|
||||
const results2 = convertTable(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);
|
||||
|
@ -191,7 +189,7 @@ describe('buildHierarchicalData convertTable', () => {
|
|||
});
|
||||
|
||||
it('should set the hits attribute for the results', () => {
|
||||
const results = convertTable(table, dimensions);
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results.slices).toHaveProperty('children');
|
||||
|
@ -226,7 +224,7 @@ describe('buildHierarchicalData convertTable', () => {
|
|||
});
|
||||
|
||||
it('should set the hits attribute for the results', () => {
|
||||
const results = convertTable(table, dimensions);
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results.slices).toHaveProperty('children');
|
||||
|
@ -261,7 +259,7 @@ describe('buildHierarchicalData convertTable', () => {
|
|||
});
|
||||
|
||||
it('should set the hits attribute for the results', () => {
|
||||
const results = convertTable(table, dimensions);
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results).toHaveProperty('names');
|
||||
|
|
|
@ -17,14 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { BuildHierarchicalDataProvider } from './hierarchical/build_hierarchical_data';
|
||||
import { AggResponsePointSeriesProvider } from './point_series/point_series';
|
||||
import { buildHierarchicalData } from './hierarchical/build_hierarchical_data';
|
||||
import { buildPointSeriesData } from './point_series/point_series';
|
||||
import { tabifyAggResponse } from './tabify/tabify';
|
||||
|
||||
export function AggResponseIndexProvider(Private) {
|
||||
return {
|
||||
hierarchical: Private(BuildHierarchicalDataProvider),
|
||||
pointSeries: Private(AggResponsePointSeriesProvider),
|
||||
tabify: tabifyAggResponse
|
||||
};
|
||||
}
|
||||
export const aggResponseIndex = {
|
||||
hierarchical: buildHierarchicalData,
|
||||
pointSeries: buildPointSeriesData,
|
||||
tabify: tabifyAggResponse
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('makeFakeXAspect', function () {
|
|||
expect(aspect)
|
||||
.to.have.property('accessor', -1)
|
||||
.and.have.property('title', 'All docs')
|
||||
.and.have.property('fieldFormatter')
|
||||
.and.have.property('format')
|
||||
.and.have.property('params');
|
||||
|
||||
});
|
||||
|
|
|
@ -17,14 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
import { getPoint } from '../_get_point';
|
||||
|
||||
describe('getPoint', function () {
|
||||
|
||||
const truthFormatted = _.constant(true);
|
||||
|
||||
const table = {
|
||||
columns: [{ id: '0' }, { id: '1' }, { id: '3' }],
|
||||
rows: [
|
||||
|
@ -80,7 +77,7 @@ describe('getPoint', function () {
|
|||
});
|
||||
|
||||
it('properly unwraps and scales values', function () {
|
||||
const seriesAspect = [{ accessor: 1, fieldFormatter: _.identity }];
|
||||
const seriesAspect = [{ accessor: 1 }];
|
||||
const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect);
|
||||
|
||||
expect(point)
|
||||
|
@ -90,12 +87,12 @@ describe('getPoint', function () {
|
|||
});
|
||||
|
||||
it('properly formats series values', function () {
|
||||
const seriesAspect = [{ accessor: 1, fieldFormatter: truthFormatted }];
|
||||
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', 'true')
|
||||
.and.have.property('series', '$2')
|
||||
.and.have.property('y', 3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('initXAxis', function () {
|
|||
const baseChart = {
|
||||
aspects: {
|
||||
x: [{
|
||||
fieldFormatter: _.constant({}),
|
||||
format: {},
|
||||
title: 'label',
|
||||
params: {}
|
||||
}]
|
||||
|
@ -38,7 +38,7 @@ describe('initXAxis', function () {
|
|||
initXAxis(chart);
|
||||
expect(chart)
|
||||
.to.have.property('xAxisLabel', 'label')
|
||||
.and.have.property('xAxisFormatter', chart.aspects.x[0].fieldFormatter);
|
||||
.and.have.property('xAxisFormat', chart.aspects.x[0].format);
|
||||
});
|
||||
|
||||
it('makes the chart ordered if the agg is ordered', function () {
|
||||
|
@ -48,7 +48,7 @@ describe('initXAxis', function () {
|
|||
initXAxis(chart);
|
||||
expect(chart)
|
||||
.to.have.property('xAxisLabel', 'label')
|
||||
.and.have.property('xAxisFormatter', chart.aspects.x[0].fieldFormatter)
|
||||
.and.have.property('xAxisFormat', chart.aspects.x[0].format)
|
||||
.and.have.property('ordered');
|
||||
});
|
||||
|
||||
|
@ -59,7 +59,7 @@ describe('initXAxis', function () {
|
|||
initXAxis(chart);
|
||||
expect(chart)
|
||||
.to.have.property('xAxisLabel', 'label')
|
||||
.and.have.property('xAxisFormatter', chart.aspects.x[0].fieldFormatter)
|
||||
.and.have.property('xAxisFormat', chart.aspects.x[0].format)
|
||||
.and.have.property('ordered');
|
||||
|
||||
expect(chart.ordered)
|
||||
|
|
|
@ -26,8 +26,8 @@ describe('initYAxis', function () {
|
|||
const baseChart = {
|
||||
aspects: {
|
||||
y: [
|
||||
{ title: 'y1', fieldFormatter: v => v },
|
||||
{ title: 'y2', fieldFormatter: v => v },
|
||||
{ title: 'y1', format: {} },
|
||||
{ title: 'y2', format: {} },
|
||||
],
|
||||
x: [{
|
||||
title: 'x'
|
||||
|
@ -42,7 +42,7 @@ 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('yAxisFormatter');
|
||||
expect(chart).to.have.property('yAxisFormat');
|
||||
});
|
||||
|
||||
it('sets the yAxisLabel', function () {
|
||||
|
@ -57,10 +57,10 @@ describe('initYAxis', function () {
|
|||
const chart = _.cloneDeep(baseChart);
|
||||
initYAxis(chart);
|
||||
|
||||
expect(chart).to.have.property('yAxisFormatter');
|
||||
expect(chart.yAxisFormatter)
|
||||
.to.be(chart.aspects.y[0].fieldFormatter)
|
||||
.and.not.be(chart.aspects.y[1].fieldFormatter);
|
||||
expect(chart).to.have.property('yAxisFormat');
|
||||
expect(chart.yAxisFormat)
|
||||
.to.be(chart.aspects.y[0].format)
|
||||
.and.not.be(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 () {
|
||||
|
|
|
@ -19,20 +19,11 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import { AggResponsePointSeriesProvider } from '../point_series';
|
||||
import { buildPointSeriesData } from '../point_series';
|
||||
|
||||
describe('pointSeriesChartDataFromTable', function () {
|
||||
this.slow(1000);
|
||||
|
||||
|
||||
let pointSeriesChartDataFromTable;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
pointSeriesChartDataFromTable = Private(AggResponsePointSeriesProvider);
|
||||
}));
|
||||
|
||||
it('handles a table with just a count', function () {
|
||||
const table = {
|
||||
columns: [{ id: '0' }],
|
||||
|
@ -40,7 +31,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
|||
{ '0': 100 }
|
||||
],
|
||||
};
|
||||
const chartData = pointSeriesChartDataFromTable(table, {
|
||||
const chartData = buildPointSeriesData(table, {
|
||||
y: [{
|
||||
accessor: 0,
|
||||
params: {},
|
||||
|
@ -72,7 +63,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
|||
y: [{ accessor: 1, params: {} }],
|
||||
};
|
||||
|
||||
const chartData = pointSeriesChartDataFromTable(table, dimensions);
|
||||
const chartData = buildPointSeriesData(table, dimensions);
|
||||
|
||||
expect(chartData).to.be.an('object');
|
||||
expect(chartData.series).to.be.an('array');
|
||||
|
@ -97,7 +88,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
|||
y: [{ accessor: 1, params: {} }, { accessor: 2, params: {} }],
|
||||
};
|
||||
|
||||
const chartData = pointSeriesChartDataFromTable(table, 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);
|
||||
|
@ -128,7 +119,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
|||
y: [{ accessor: 2, params: {} }, { accessor: 3, params: {} }],
|
||||
};
|
||||
|
||||
const chartData = pointSeriesChartDataFromTable(table, dimensions);
|
||||
const chartData = buildPointSeriesData(table, dimensions);
|
||||
expect(chartData).to.be.an('object');
|
||||
expect(chartData.series).to.be.an('array');
|
||||
// one series for each extension, and then one for each metric inside
|
||||
|
|
|
@ -43,25 +43,6 @@ describe('orderedDateAxis', function () {
|
|||
}
|
||||
};
|
||||
|
||||
describe('xAxisFormatter', function () {
|
||||
it('sets the xAxisFormatter', function () {
|
||||
const args = _.cloneDeep(baseArgs);
|
||||
orderedDateAxis(args.chart);
|
||||
|
||||
expect(args.chart).to.have.property('xAxisFormatter');
|
||||
expect(args.chart.xAxisFormatter).to.be.a('function');
|
||||
});
|
||||
|
||||
it('formats values using moment, and returns strings', function () {
|
||||
const args = _.cloneDeep(baseArgs);
|
||||
orderedDateAxis(args.chart);
|
||||
|
||||
const val = '2014-08-06T12:34:01';
|
||||
expect(args.chart.xAxisFormatter(val))
|
||||
.to.be(moment(val).format('hh:mm:ss'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ordered object', function () {
|
||||
it('sets date: true', function () {
|
||||
const args = _.cloneDeep(baseArgs);
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('tooltipFormatter', function () {
|
|||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
tooltipFormatter = Private(PointSeriesTooltipFormatter);
|
||||
tooltipFormatter = Private(PointSeriesTooltipFormatter)();
|
||||
}));
|
||||
|
||||
function cell($row, i) {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function addToSiri(series, point, id, label, formatter) {
|
||||
export function addToSiri(series, point, id, label, format) {
|
||||
id = id == null ? '' : id + '';
|
||||
|
||||
if (series.has(id)) {
|
||||
|
@ -30,6 +30,6 @@ export function addToSiri(series, point, id, label, formatter) {
|
|||
label: label == null ? id : label,
|
||||
count: 0,
|
||||
values: [point],
|
||||
yAxisFormatter: formatter,
|
||||
format: format,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,6 @@ export function makeFakeXAspect() {
|
|||
defaultMessage: 'All docs'
|
||||
}),
|
||||
params: {},
|
||||
fieldFormatter: () => '',
|
||||
format: {}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
||||
import { makeFakeXAspect } from './_fake_x_aspect';
|
||||
|
||||
/**
|
||||
|
@ -40,7 +39,6 @@ export function getAspects(table, dimensions) {
|
|||
if (!column) {
|
||||
return;
|
||||
}
|
||||
const formatter = getFormat(d.format);
|
||||
if (!aspects[name]) {
|
||||
aspects[name] = [];
|
||||
}
|
||||
|
@ -48,7 +46,7 @@ export function getAspects(table, dimensions) {
|
|||
accessor: column.id,
|
||||
column: d.accessor,
|
||||
title: column.name,
|
||||
fieldFormatter: val => formatter.convert(val, 'text'),
|
||||
format: d.format,
|
||||
params: d.params,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities';
|
||||
|
||||
export function getPoint(table, x, series, yScale, row, rowIndex, y, z) {
|
||||
const zRow = z && row[z.accessor];
|
||||
|
@ -72,7 +73,10 @@ export function getPoint(table, x, series, yScale, row, rowIndex, y, z) {
|
|||
|
||||
if (series) {
|
||||
const seriesArray = series.length ? series : [ series ];
|
||||
point.series = seriesArray.map(s => s.fieldFormatter(row[s.accessor])).join(' - ');
|
||||
point.series = seriesArray.map(s => {
|
||||
const fieldFormatter = getFormat(s.format);
|
||||
return fieldFormatter.convert(row[s.accessor]);
|
||||
}).join(' - ');
|
||||
} else if (y) {
|
||||
// If the data is not split up with a series aspect, then
|
||||
// each point's "series" becomes the y-agg that produced it
|
||||
|
|
|
@ -32,7 +32,7 @@ export function getSeries(table, chart) {
|
|||
if (!multiY) {
|
||||
const point = partGetPoint(row, rowIndex, aspects.y[0], aspects.z);
|
||||
const id = `${point.series}-${aspects.y[0].accessor}`;
|
||||
if (point) addToSiri(series, point, id, point.series, aspects.y[0].fieldFormatter);
|
||||
if (point) addToSiri(series, point, id, point.series, aspects.y[0].format);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ export function getSeries(table, chart) {
|
|||
seriesLabel = prefix + seriesLabel;
|
||||
}
|
||||
|
||||
addToSiri(series, point, seriesId, seriesLabel, y.fieldFormatter);
|
||||
addToSiri(series, point, seriesId, seriesLabel, y.format);
|
||||
});
|
||||
|
||||
}, new Map())
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
export function initXAxis(chart) {
|
||||
const x = chart.aspects.x[0];
|
||||
chart.xAxisFormatter = x.fieldFormatter ? x.fieldFormatter : String;
|
||||
chart.xAxisFormat = x.format;
|
||||
chart.xAxisLabel = x.title;
|
||||
if (x.params.date) {
|
||||
chart.ordered = {
|
||||
|
|
|
@ -22,17 +22,17 @@ export function initYAxis(chart) {
|
|||
|
||||
if (Array.isArray(y)) {
|
||||
// TODO: vis option should allow choosing this format
|
||||
chart.yAxisFormatter = y[0].fieldFormatter;
|
||||
chart.yAxisFormat = y[0].format;
|
||||
chart.yAxisLabel = y.length > 1 ? '' : y[0].title;
|
||||
}
|
||||
|
||||
const z = chart.aspects.series;
|
||||
if (z) {
|
||||
if (Array.isArray(z)) {
|
||||
chart.zAxisFormatter = z[0].fieldFormatter;
|
||||
chart.zAxisFormat = z[0].format;
|
||||
chart.zAxisLabel = '';
|
||||
} else {
|
||||
chart.zAxisFormatter = z.fieldFormatter;
|
||||
chart.zAxisFormat = z.format;
|
||||
chart.zAxisLabel = z.title;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,15 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
// import moment from 'moment';
|
||||
|
||||
export function orderedDateAxis(chart) {
|
||||
const x = chart.aspects.x[0];
|
||||
const { format, bounds } = x.params;
|
||||
|
||||
chart.xAxisFormatter = function (val) {
|
||||
return moment(val).format(format);
|
||||
};
|
||||
const { bounds } = x.params;
|
||||
|
||||
chart.ordered.date = true;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities';
|
||||
|
||||
export function PointSeriesTooltipFormatter($compile, $rootScope) {
|
||||
|
||||
|
@ -25,38 +26,41 @@ export function PointSeriesTooltipFormatter($compile, $rootScope) {
|
|||
const $tooltip = $(require('ui/agg_response/point_series/_tooltip.html'));
|
||||
$compile($tooltip)($tooltipScope);
|
||||
|
||||
return function tooltipFormatter(event) {
|
||||
const data = event.data;
|
||||
const datum = event.datum;
|
||||
if (!datum) return '';
|
||||
return function () {
|
||||
return function tooltipFormatter(event) {
|
||||
const data = event.data;
|
||||
const datum = event.datum;
|
||||
if (!datum) return '';
|
||||
|
||||
const details = $tooltipScope.details = [];
|
||||
const details = $tooltipScope.details = [];
|
||||
|
||||
const addDetail = (label, value) => details.push({ label, value });
|
||||
const addDetail = (label, value) => details.push({ label, value });
|
||||
|
||||
datum.extraMetrics.forEach(metric => {
|
||||
addDetail(metric.label, metric.value);
|
||||
});
|
||||
datum.extraMetrics.forEach(metric => {
|
||||
addDetail(metric.label, metric.value);
|
||||
});
|
||||
|
||||
if (datum.x) {
|
||||
addDetail(data.xAxisLabel, data.xAxisFormatter(datum.x));
|
||||
}
|
||||
if (datum.y) {
|
||||
const value = datum.yScale ? datum.yScale * datum.y : datum.y;
|
||||
addDetail(data.yAxisLabel, data.yAxisFormatter(value));
|
||||
}
|
||||
if (datum.z) {
|
||||
addDetail(data.zAxisLabel, data.zAxisFormatter(datum.z));
|
||||
}
|
||||
if (datum.series && datum.parent) {
|
||||
const dimension = datum.parent;
|
||||
addDetail(dimension.title, dimension.fieldFormatter(datum.series));
|
||||
}
|
||||
if (datum.tableRaw) {
|
||||
addDetail(datum.tableRaw.title, datum.tableRaw.value);
|
||||
}
|
||||
if (datum.x) {
|
||||
addDetail(data.xAxisLabel, data.xAxisFormatter(datum.x));
|
||||
}
|
||||
if (datum.y) {
|
||||
const value = datum.yScale ? datum.yScale * datum.y : datum.y;
|
||||
addDetail(data.yAxisLabel, data.yAxisFormatter(value));
|
||||
}
|
||||
if (datum.z) {
|
||||
addDetail(data.zAxisLabel, data.zAxisFormatter(datum.z));
|
||||
}
|
||||
if (datum.series && datum.parent) {
|
||||
const dimension = datum.parent;
|
||||
const seriesFormatter = getFormat(dimension.format);
|
||||
addDetail(dimension.title, seriesFormatter.convert(datum.series));
|
||||
}
|
||||
if (datum.tableRaw) {
|
||||
addDetail(datum.tableRaw.title, datum.tableRaw.value);
|
||||
}
|
||||
|
||||
$tooltipScope.$apply();
|
||||
return $tooltip[0].outerHTML;
|
||||
$tooltipScope.$apply();
|
||||
return $tooltip[0].outerHTML;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { AggResponsePointSeriesProvider } from './point_series';
|
||||
export { buildPointSeriesData } from './point_series';
|
||||
|
|
|
@ -22,29 +22,22 @@ import { getAspects } from './_get_aspects';
|
|||
import { initYAxis } from './_init_y_axis';
|
||||
import { initXAxis } from './_init_x_axis';
|
||||
import { orderedDateAxis } from './_ordered_date_axis';
|
||||
import { PointSeriesTooltipFormatter } from './_tooltip_formatter';
|
||||
|
||||
export function AggResponsePointSeriesProvider(Private) {
|
||||
|
||||
const tooltipFormatter = Private(PointSeriesTooltipFormatter);
|
||||
|
||||
return function pointSeriesChartDataFromTable(table, dimensions) {
|
||||
const chart = {
|
||||
aspects: getAspects(table, dimensions),
|
||||
tooltipFormatter
|
||||
};
|
||||
|
||||
initXAxis(chart);
|
||||
initYAxis(chart);
|
||||
|
||||
|
||||
if (chart.aspects.x[0].params.date) {
|
||||
orderedDateAxis(chart);
|
||||
}
|
||||
|
||||
chart.series = getSeries(table, chart);
|
||||
|
||||
delete chart.aspects;
|
||||
return chart;
|
||||
export const buildPointSeriesData = (table, dimensions) => {
|
||||
const chart = {
|
||||
aspects: getAspects(table, dimensions),
|
||||
};
|
||||
}
|
||||
|
||||
initXAxis(chart);
|
||||
initYAxis(chart);
|
||||
|
||||
|
||||
if (chart.aspects.x[0].params.date) {
|
||||
orderedDateAxis(chart);
|
||||
}
|
||||
|
||||
chart.series = getSeries(table, chart);
|
||||
|
||||
delete chart.aspects;
|
||||
return chart;
|
||||
};
|
||||
|
|
|
@ -52,6 +52,12 @@ describe('brushEvent', () => {
|
|||
};
|
||||
|
||||
const baseEvent = {
|
||||
aggConfigs: [{
|
||||
params: {},
|
||||
getIndexPattern: () => ({
|
||||
timeFieldName: 'time',
|
||||
})
|
||||
}],
|
||||
data: {
|
||||
fieldFormatter: _.constant({}),
|
||||
series: [
|
||||
|
@ -64,12 +70,6 @@ describe('brushEvent', () => {
|
|||
columns: [
|
||||
{
|
||||
id: '1',
|
||||
aggConfig: {
|
||||
params: {},
|
||||
getIndexPattern: () => ({
|
||||
timeFieldName: 'time',
|
||||
})
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ describe('brushEvent', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
dateEvent = _.cloneDeep(baseEvent);
|
||||
dateEvent.data.series[0].values[0].xRaw.table.columns[0].aggConfig.params.field = dateField;
|
||||
dateEvent.aggConfigs[0].params.field = dateField;
|
||||
dateEvent.data.ordered = { date: true };
|
||||
});
|
||||
|
||||
|
@ -144,7 +144,7 @@ describe('brushEvent', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
dateEvent = _.cloneDeep(baseEvent);
|
||||
dateEvent.data.series[0].values[0].xRaw.table.columns[0].aggConfig.params.field = dateField;
|
||||
dateEvent.aggConfigs[0].params.field = dateField;
|
||||
dateEvent.data.ordered = { date: true };
|
||||
});
|
||||
|
||||
|
@ -200,7 +200,7 @@ describe('brushEvent', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
numberEvent = _.cloneDeep(baseEvent);
|
||||
numberEvent.data.series[0].values[0].xRaw.table.columns[0].aggConfig.params.field = numberField;
|
||||
numberEvent.aggConfigs[0].params.field = numberField;
|
||||
numberEvent.data.ordered = { date: false };
|
||||
});
|
||||
|
||||
|
|
|
@ -30,8 +30,10 @@ export function onBrushEvent(event, $state) {
|
|||
if (!xRaw) return;
|
||||
const column = xRaw.table.columns[xRaw.column];
|
||||
if (!column) return;
|
||||
const indexPattern = column.aggConfig.getIndexPattern();
|
||||
const field = column.aggConfig.params.field;
|
||||
const aggConfig = event.aggConfigs[xRaw.column];
|
||||
if (!aggConfig) return;
|
||||
const indexPattern = aggConfig.getIndexPattern();
|
||||
const field = aggConfig.params.field;
|
||||
if (!field) return;
|
||||
const fieldName = field.name;
|
||||
|
||||
|
|
|
@ -18,21 +18,13 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { AggResponseIndexProvider } from '../../../agg_response';
|
||||
import { VislibSeriesResponseHandlerProvider } from '../../response_handlers/vislib';
|
||||
import { aggResponseIndex } from '../../../agg_response';
|
||||
import { VislibSeriesResponseHandlerProvider as vislibReponseHandler } from '../../response_handlers/vislib';
|
||||
|
||||
describe('renderbot#buildChartData', function () {
|
||||
let buildChartData;
|
||||
let aggResponse;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
aggResponse = Private(AggResponseIndexProvider);
|
||||
buildChartData = Private(VislibSeriesResponseHandlerProvider).handler;
|
||||
}));
|
||||
const buildChartData = vislibReponseHandler().handler;
|
||||
|
||||
describe('for hierarchical vis', function () {
|
||||
it('defers to hierarchical aggResponse converter', function () {
|
||||
|
@ -43,7 +35,7 @@ describe('renderbot#buildChartData', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const stub = sinon.stub(aggResponse, 'hierarchical').returns(football);
|
||||
const stub = sinon.stub(aggResponseIndex, 'hierarchical').returns(football);
|
||||
expect(buildChartData.call(renderbot, football)).to.be(football);
|
||||
expect(stub).to.have.property('callCount', 1);
|
||||
expect(stub.firstCall.args[0]).to.be(renderbot.vis);
|
||||
|
@ -60,7 +52,7 @@ describe('renderbot#buildChartData', function () {
|
|||
};
|
||||
const football = { tables: [], hits: { total: 1 } };
|
||||
|
||||
const stub = sinon.stub(aggResponse, 'tabify').returns(football);
|
||||
const stub = sinon.stub(aggResponseIndex, 'tabify').returns(football);
|
||||
expect(buildChartData.call(renderbot, football)).to.eql({ rows: [], hits: 1 });
|
||||
expect(stub).to.have.property('callCount', 1);
|
||||
expect(stub.firstCall.args[0]).to.be(renderbot.vis);
|
||||
|
@ -80,7 +72,7 @@ describe('renderbot#buildChartData', function () {
|
|||
const esResp = { hits: { total: 1 } };
|
||||
const tabbed = { tables: [ {}] };
|
||||
|
||||
sinon.stub(aggResponse, 'tabify').returns(tabbed);
|
||||
sinon.stub(aggResponseIndex, 'tabify').returns(tabbed);
|
||||
expect(buildChartData.call(renderbot, esResp)).to.eql(chart);
|
||||
});
|
||||
|
||||
|
@ -98,7 +90,7 @@ describe('renderbot#buildChartData', function () {
|
|||
}
|
||||
};
|
||||
|
||||
sinon.stub(aggResponse, 'tabify').returns({
|
||||
sinon.stub(aggResponseIndex, 'tabify').returns({
|
||||
tables: [
|
||||
{
|
||||
aggConfig: { params: { row: true } },
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { BuildHierarchicalDataProvider } from '../../agg_response/hierarchical/build_hierarchical_data';
|
||||
import { AggResponsePointSeriesProvider } from '../../agg_response/point_series/point_series';
|
||||
import { buildHierarchicalData } from '../../agg_response/hierarchical/build_hierarchical_data';
|
||||
import { buildPointSeriesData } from '../../agg_response/point_series/point_series';
|
||||
import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers';
|
||||
import { LegacyResponseHandlerProvider as legacyResponseHandlerProvider } from './legacy';
|
||||
|
||||
|
@ -78,18 +78,14 @@ const handlerFunction = function (convertTable) {
|
|||
};
|
||||
};
|
||||
|
||||
const VislibSeriesResponseHandlerProvider = function (Private) {
|
||||
const buildPointSeriesData = Private(AggResponsePointSeriesProvider);
|
||||
|
||||
const VislibSeriesResponseHandlerProvider = function () {
|
||||
return {
|
||||
name: 'vislib_series',
|
||||
handler: handlerFunction(buildPointSeriesData)
|
||||
};
|
||||
};
|
||||
|
||||
const VislibSlicesResponseHandlerProvider = function (Private) {
|
||||
const buildHierarchicalData = Private(BuildHierarchicalDataProvider);
|
||||
|
||||
const VislibSlicesResponseHandlerProvider = function () {
|
||||
return {
|
||||
name: 'vislib_slices',
|
||||
handler: handlerFunction(buildHierarchicalData)
|
||||
|
|
|
@ -96,7 +96,9 @@ export function VisProvider(Private, indexPatterns, getAppState) {
|
|||
|
||||
updateVisualizationConfig(state.params, this.params);
|
||||
|
||||
this.aggs = new AggConfigs(this.indexPattern, state.aggs, this.type.schemas.all);
|
||||
if (state.aggs || !this.aggs) {
|
||||
this.aggs = new AggConfigs(this.indexPattern, state.aggs, this.type.schemas.all);
|
||||
}
|
||||
}
|
||||
|
||||
setState(state, updateCurrentState = true) {
|
||||
|
|
|
@ -25,14 +25,12 @@ import 'plugins/kbn_vislib_vis_types/controls/gauge_options';
|
|||
import 'plugins/kbn_vislib_vis_types/controls/point_series';
|
||||
import './vislib_vis_legend';
|
||||
import { BaseVisTypeProvider } from './base_vis_type';
|
||||
import { AggResponsePointSeriesProvider } from '../../agg_response/point_series/point_series';
|
||||
import VislibProvider from '../../vislib';
|
||||
import { VisFiltersProvider } from '../vis_filters';
|
||||
import $ from 'jquery';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
export function VislibVisTypeProvider(Private, $rootScope, $timeout, $compile) {
|
||||
const pointSeries = Private(AggResponsePointSeriesProvider);
|
||||
const vislib = Private(VislibProvider);
|
||||
const visFilters = Private(VisFiltersProvider);
|
||||
const BaseVisType = Private(BaseVisTypeProvider);
|
||||
|
@ -119,9 +117,6 @@ export function VislibVisTypeProvider(Private, $rootScope, $timeout, $compile) {
|
|||
opts.responseHandler = 'vislib_series';
|
||||
opts.responseHandlerConfig = { asAggConfigResults: true };
|
||||
}
|
||||
if (!opts.responseConverter) {
|
||||
opts.responseConverter = pointSeries;
|
||||
}
|
||||
opts.events = defaultsDeep({}, opts.events, {
|
||||
filterBucket: {
|
||||
defaultAction: visFilters.filter,
|
||||
|
|
|
@ -237,7 +237,7 @@ describe('Vislib Data Class Test Suite', function () {
|
|||
describe('getVisData', function () {
|
||||
it('should return the rows property', function () {
|
||||
const visData = data.getVisData();
|
||||
expect(visData).to.eql(geohashGridData.rows);
|
||||
expect(visData[0].title).to.eql(geohashGridData.rows[0].title);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import { VislibComponentsZeroInjectionInjectZerosProvider } from '../components/
|
|||
import { VislibComponentsZeroInjectionOrderedXKeysProvider } from '../components/zero_injection/ordered_x_keys';
|
||||
import { VislibComponentsLabelsLabelsProvider } from '../components/labels/labels';
|
||||
import { VislibComponentsColorColorProvider } from '../../vis/components/color/color';
|
||||
import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities';
|
||||
|
||||
export function VislibLibDataProvider(Private) {
|
||||
|
||||
|
@ -59,6 +60,7 @@ export function VislibLibDataProvider(Private) {
|
|||
newData[key] = data[key];
|
||||
} else {
|
||||
newData[key] = data[key].map(seri => {
|
||||
const converter = getFormat(seri.format);
|
||||
return {
|
||||
id: seri.id,
|
||||
label: seri.label,
|
||||
|
@ -68,11 +70,19 @@ export function VislibLibDataProvider(Private) {
|
|||
newVal.series = val.series || seri.label;
|
||||
return newVal;
|
||||
}),
|
||||
yAxisFormatter: seri.yAxisFormatter,
|
||||
yAxisFormatter: val => converter.convert(val)
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const xConverter = getFormat(newData.xAxisFormat);
|
||||
const yConverter = getFormat(newData.yAxisFormat);
|
||||
const zConverter = getFormat(newData.zAxisFormat);
|
||||
newData.xAxisFormatter = val => xConverter.convert(val);
|
||||
newData.yAxisFormatter = val => yConverter.convert(val);
|
||||
newData.zAxisFormatter = val => zConverter.convert(val);
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ import _ from 'lodash';
|
|||
import { dataLabel } from '../lib/_data_label';
|
||||
import { VislibLibDispatchProvider } from '../lib/dispatch';
|
||||
import { TooltipProvider } from '../../vis/components/tooltip';
|
||||
import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities';
|
||||
import { HierarchicalTooltipFormatterProvider } from '../../agg_response/hierarchical/_hierarchical_tooltip_formatter';
|
||||
import { PointSeriesTooltipFormatter } from '../../agg_response/point_series/_tooltip_formatter';
|
||||
|
||||
export function VislibVisualizationsChartProvider(Private) {
|
||||
|
||||
|
@ -45,12 +48,16 @@ export function VislibVisualizationsChartProvider(Private) {
|
|||
|
||||
const events = this.events = new Dispatch(handler);
|
||||
|
||||
const fieldFormatter = getFormat(this.handler.data.get('tooltipFormatter'));
|
||||
const tooltipFormatterProvider = this.handler.visConfig.get('type') === 'pie' ?
|
||||
Private(HierarchicalTooltipFormatterProvider) : Private(PointSeriesTooltipFormatter);
|
||||
const tooltipFormatter = tooltipFormatterProvider(fieldFormatter);
|
||||
|
||||
if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) {
|
||||
const $el = this.handler.el;
|
||||
const formatter = this.handler.data.get('tooltipFormatter');
|
||||
|
||||
// Add tooltip
|
||||
this.tooltip = new Tooltip('chart', $el, formatter, events);
|
||||
this.tooltip = new Tooltip('chart', $el, tooltipFormatter, events);
|
||||
this.tooltips.push(this.tooltip);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
.visualization {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
padding: $euiSizeS;
|
||||
|
|
|
@ -56,16 +56,11 @@ export class Visualization extends React.Component<VisualizationProps> {
|
|||
constructor(props: VisualizationProps) {
|
||||
super(props);
|
||||
|
||||
const { vis, uiState, listenOnChange } = props;
|
||||
|
||||
vis._setUiState(props.uiState);
|
||||
if (listenOnChange) {
|
||||
uiState.on('change', this.onUiStateChanged);
|
||||
}
|
||||
props.vis._setUiState(props.uiState);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { vis, visData, onInit, uiState } = this.props;
|
||||
const { vis, visData, onInit, uiState, listenOnChange } = this.props;
|
||||
|
||||
const noResults = this.showNoResultsMessage(vis, visData);
|
||||
const requestError = shouldShowRequestErrorMessage(vis, visData);
|
||||
|
@ -77,7 +72,13 @@ export class Visualization extends React.Component<VisualizationProps> {
|
|||
) : noResults ? (
|
||||
<VisualizationNoResults onInit={onInit} />
|
||||
) : (
|
||||
<VisualizationChart vis={vis} visData={visData} onInit={onInit} uiState={uiState} />
|
||||
<VisualizationChart
|
||||
vis={vis}
|
||||
visData={visData}
|
||||
onInit={onInit}
|
||||
uiState={uiState}
|
||||
listenOnChange={listenOnChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -89,28 +90,4 @@ export class Visualization extends React.Component<VisualizationProps> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.props.uiState.off('change', this.onUiStateChanged);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: VisualizationProps) {
|
||||
const { listenOnChange } = this.props;
|
||||
// If the listenOnChange prop changed, we need to register or deregister from uiState
|
||||
if (prevProps.listenOnChange !== listenOnChange) {
|
||||
if (listenOnChange) {
|
||||
this.props.uiState.on('change', this.onUiStateChanged);
|
||||
} else {
|
||||
this.props.uiState.off('change', this.onUiStateChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In case something in the uiState changed, we need to force a redraw of
|
||||
* the visualization, since these changes could effect visualization rendering.
|
||||
*/
|
||||
private onUiStateChanged() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ interface VisualizationChartProps {
|
|||
uiState: PersistedState;
|
||||
vis: Vis;
|
||||
visData: any;
|
||||
listenOnChange: boolean;
|
||||
}
|
||||
|
||||
class VisualizationChart extends React.Component<VisualizationChartProps> {
|
||||
|
@ -123,6 +124,10 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
this.resizeChecker = new ResizeChecker(this.containerDiv.current);
|
||||
this.resizeChecker.on('resize', () => this.startRenderVisualization());
|
||||
|
||||
if (this.props.listenOnChange) {
|
||||
this.props.uiState.on('change', this.onUiStateChanged);
|
||||
}
|
||||
|
||||
this.startRenderVisualization();
|
||||
}
|
||||
|
||||
|
@ -142,6 +147,10 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
}
|
||||
}
|
||||
|
||||
private onUiStateChanged = () => {
|
||||
this.startRenderVisualization();
|
||||
};
|
||||
|
||||
private startRenderVisualization(): void {
|
||||
if (this.containerDiv.current && this.chartDiv.current) {
|
||||
this.renderSubject.next({
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { debounce, forEach } from 'lodash';
|
||||
import { debounce, forEach, get } from 'lodash';
|
||||
// @ts-ignore
|
||||
import { renderFunctionsRegistry } from 'plugins/interpreter/render_functions_registry';
|
||||
import * as Rx from 'rxjs';
|
||||
import { share } from 'rxjs/operators';
|
||||
import { Inspector } from '../../inspector';
|
||||
|
@ -62,6 +64,7 @@ export class EmbeddedVisualizeHandler {
|
|||
public readonly data$: Rx.Observable<any>;
|
||||
public readonly inspectorAdapters: Adapters = {};
|
||||
private vis: Vis;
|
||||
private handlers: any;
|
||||
private loaded: boolean = false;
|
||||
private destroyed: boolean = false;
|
||||
|
||||
|
@ -126,6 +129,12 @@ export class EmbeddedVisualizeHandler {
|
|||
}
|
||||
this.uiState = this.vis.getUiState();
|
||||
|
||||
this.handlers = {
|
||||
vis: this.vis,
|
||||
uiState: this.uiState,
|
||||
onDestroy: (fn: () => never) => (this.handlers.destroyFn = fn),
|
||||
};
|
||||
|
||||
this.vis.on('update', this.handleVisUpdate);
|
||||
this.vis.on('reload', this.reload);
|
||||
this.uiState.on('change', this.onUiStateChange);
|
||||
|
@ -146,8 +155,9 @@ export class EmbeddedVisualizeHandler {
|
|||
}
|
||||
});
|
||||
|
||||
this.vis.eventsSubject = new Rx.Subject();
|
||||
this.events$ = this.vis.eventsSubject.asObservable().pipe(share());
|
||||
this.handlers.eventsSubject = new Rx.Subject();
|
||||
this.vis.eventsSubject = this.handlers.eventsSubject;
|
||||
this.events$ = this.handlers.eventsSubject.asObservable().pipe(share());
|
||||
this.events$.subscribe(event => {
|
||||
if (this.actions[event.name]) {
|
||||
event.data.aggConfigs = getTableAggs(this.vis);
|
||||
|
@ -215,6 +225,9 @@ export class EmbeddedVisualizeHandler {
|
|||
this.uiState.off('change', this.onUiStateChange);
|
||||
visualizationLoader.destroy(this.element);
|
||||
this.renderCompleteHelper.destroy();
|
||||
if (this.handlers.destroyFn) {
|
||||
this.handlers.destroyFn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,21 +243,20 @@ export class EmbeddedVisualizeHandler {
|
|||
* renders visualization with provided data
|
||||
* @param visData: visualization data
|
||||
*/
|
||||
public render = (pipelineResponse: any = null) => {
|
||||
let visData;
|
||||
if (pipelineResponse) {
|
||||
if (!pipelineResponse.value) {
|
||||
throw new Error(pipelineResponse.error);
|
||||
}
|
||||
visData = pipelineResponse.value.visData || pipelineResponse.value;
|
||||
if (pipelineResponse.value.visConfig) {
|
||||
this.vis.params = pipelineResponse.value.visConfig.params;
|
||||
}
|
||||
public render = (pipelineResponse: any = {}) => {
|
||||
// TODO: we have this weird situation when we need to render first, and then we call fetch and render ....
|
||||
// we need to get rid of that ....
|
||||
|
||||
const renderer = renderFunctionsRegistry.get(get(pipelineResponse, 'as', 'visualization'));
|
||||
if (!renderer) {
|
||||
return;
|
||||
}
|
||||
return visualizationLoader
|
||||
.render(this.element, this.vis, visData, this.uiState, {
|
||||
listenOnChange: false,
|
||||
})
|
||||
renderer
|
||||
.render(
|
||||
this.element,
|
||||
pipelineResponse.value || { visType: this.vis.type.name },
|
||||
this.handlers
|
||||
)
|
||||
.then(() => {
|
||||
if (!this.loaded) {
|
||||
this.loaded = true;
|
||||
|
|
|
@ -22,9 +22,9 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct
|
|||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function without splits or buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[0,1],\\"buckets\\":[]}}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function with buckets 1`] = `"tagcloud visConfig='{\\"metrics\\":[0],\\"bucket\\":1}' "`;
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function with buckets 1`] = `"tagcloud visConfig='{\\"metric\\":0,\\"bucket\\":1}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function without buckets 1`] = `"tagcloud visConfig='{\\"metrics\\":[0]}' "`;
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function without buckets 1`] = `"tagcloud visConfig='{\\"metric\\":0}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":0,\\"geohash\\":1,\\"geocentroid\\":3}' "`;
|
||||
|
||||
|
|
|
@ -139,7 +139,8 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
});
|
||||
|
||||
describe('handles tagcloud function', () => {
|
||||
const params = { metrics: {} };
|
||||
const params = {};
|
||||
|
||||
it('without buckets', () => {
|
||||
const schemas = { metric: [0] };
|
||||
const actual = buildPipelineVisFunction.tagcloud({ params }, schemas);
|
||||
|
|
|
@ -234,7 +234,7 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
},
|
||||
tagcloud: (visState, schemas) => {
|
||||
const visConfig = visState.params;
|
||||
visConfig.metrics = schemas.metric;
|
||||
visConfig.metric = schemas.metric[0];
|
||||
if (schemas.segment) {
|
||||
visConfig.bucket = schemas.segment[0];
|
||||
}
|
||||
|
|
|
@ -103,6 +103,9 @@ export const getFormat = (mapping: any) => {
|
|||
};
|
||||
|
||||
export const getTableAggs = (vis: Vis): AggConfig[] => {
|
||||
if (!vis.aggs || !vis.aggs.getResponseAggs) {
|
||||
return [];
|
||||
}
|
||||
const columns = tabifyGetColumns(vis.aggs.getResponseAggs(), !vis.isHierarchical());
|
||||
return columns.map((c: any) => c.aggConfig);
|
||||
};
|
||||
|
|
151
src/ui/public/visualize/loader/vis.js
Normal file
151
src/ui/public/visualize/loader/vis.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name Vis
|
||||
*
|
||||
* @description This class consists of aggs, params, listeners, title, and type.
|
||||
* - Aggs: Instances of AggConfig.
|
||||
* - Params: The settings in the Options tab.
|
||||
*
|
||||
* Not to be confused with vislib/vis.js.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import _ from 'lodash';
|
||||
import { VisTypesRegistryProvider } from '../../registry/vis_types';
|
||||
import { PersistedState } from '../../persisted_state';
|
||||
import { FilterBarQueryFilterProvider } from '../../filter_bar/query_filter';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
export function VisProvider(Private, indexPatterns, getAppState) {
|
||||
const visTypes = Private(VisTypesRegistryProvider);
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
|
||||
class Vis extends EventEmitter {
|
||||
constructor(visState = { type: 'histogram' }) {
|
||||
super();
|
||||
|
||||
this._setUiState(new PersistedState());
|
||||
this.setState(visState);
|
||||
|
||||
// Session state is for storing information that is transitory, and will not be saved with the visualization.
|
||||
// For instance, map bounds, which depends on the view port, browser window size, etc.
|
||||
this.sessionState = {};
|
||||
|
||||
this.API = {
|
||||
indexPatterns: indexPatterns,
|
||||
timeFilter: timefilter,
|
||||
queryFilter: queryFilter,
|
||||
events: {
|
||||
filter: data => {
|
||||
if (!this.eventsSubject) return;
|
||||
this.eventsSubject.next({ name: 'filterBucket', data });
|
||||
},
|
||||
brush: data => {
|
||||
if (!this.eventsSubject) return;
|
||||
this.eventsSubject.next({ name: 'brush', data });
|
||||
},
|
||||
},
|
||||
getAppState,
|
||||
};
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
this.title = state.title || '';
|
||||
const type = state.type || this.type;
|
||||
if (_.isString(type)) {
|
||||
this.type = visTypes.byName[type];
|
||||
if (!this.type) {
|
||||
throw new Error(`Invalid type "${type}"`);
|
||||
}
|
||||
} else {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
this.params = _.defaultsDeep({},
|
||||
_.cloneDeep(state.params || {}),
|
||||
_.cloneDeep(this.type.visConfig.defaults || {})
|
||||
);
|
||||
}
|
||||
|
||||
setCurrentState(state) {
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {
|
||||
title: this.title,
|
||||
type: this.type.name,
|
||||
params: _.cloneDeep(this.params),
|
||||
};
|
||||
}
|
||||
|
||||
updateState() {
|
||||
this.emit('update');
|
||||
}
|
||||
|
||||
forceReload() {
|
||||
this.emit('reload');
|
||||
}
|
||||
|
||||
isHierarchical() {
|
||||
if (_.isFunction(this.type.hierarchicalData)) {
|
||||
return !!this.type.hierarchicalData(this);
|
||||
} else {
|
||||
return !!this.type.hierarchicalData;
|
||||
}
|
||||
}
|
||||
|
||||
hasUiState() {
|
||||
return !!this.__uiState;
|
||||
}
|
||||
|
||||
/***
|
||||
* this should not be used outside of visualize
|
||||
* @param uiState
|
||||
* @private
|
||||
*/
|
||||
_setUiState(uiState) {
|
||||
if (uiState instanceof PersistedState) {
|
||||
this.__uiState = uiState;
|
||||
}
|
||||
}
|
||||
|
||||
getUiState() {
|
||||
return this.__uiState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently this is only used to extract map-specific information
|
||||
* (e.g. mapZoom, mapCenter).
|
||||
*/
|
||||
uiStateVal(key, val) {
|
||||
if (this.hasUiState()) {
|
||||
if (_.isUndefined(val)) {
|
||||
return this.__uiState.get(key);
|
||||
}
|
||||
return this.__uiState.set(key, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
return Vis;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue