Prepare for cutover to vis data loader (#29873)

This commit is contained in:
Luke Elmers 2019-02-04 11:03:13 -07:00 committed by GitHub
parent 6afcc28c3b
commit 22933775ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 154 additions and 37 deletions

View file

@ -75,6 +75,34 @@ describe('build query', function () {
expect(result).to.eql(expectedResult);
});
it('should accept queries and filters as either single objects or arrays', function () {
const queries = { query: 'extension:jpg', language: 'lucene' };
const filters = {
match_all: {},
meta: { type: 'match_all' },
};
const config = {
allowLeadingWildcards: true,
queryStringOptions: {},
};
const expectedResult = {
bool: {
must: [
decorateQuery(luceneStringToDsl('extension:jpg'), config.queryStringOptions),
{ match_all: {} },
],
filter: [],
should: [],
must_not: [],
}
};
const result = buildEsQuery(indexPattern, queries, filters, config);
expect(result).to.eql(expectedResult);
});
});
});

View file

@ -56,7 +56,7 @@ describe('build query', function () {
const oldQuery = { query: 'is(foo, bar)', language: 'kuery' };
expect(buildQueryFromKuery).withArgs(indexPattern, [oldQuery], true).to.throwError(
/It looks like you're using an outdated Kuery syntax./
/OutdatedKuerySyntaxError/
);
});

View file

@ -24,8 +24,8 @@ import { buildQueryFromLucene } from './from_lucene';
/**
* @param indexPattern
* @param queries - an array of query objects. Each query has a language property and a query property.
* @param filters - an array of filter objects
* @param queries - a query object or array of query objects. Each query has a language property and a query property.
* @param filters - a filter object or array of filter objects
* @param config - an objects with query:allowLeadingWildcards and query:queryString:options UI
* settings in form of { allowLeadingWildcards, queryStringOptions }
*/
@ -33,15 +33,18 @@ export function buildEsQuery(
indexPattern,
queries = [],
filters = [],
{
allowLeadingWildcards = false,
queryStringOptions = {},
config = {
allowLeadingWildcards: false,
queryStringOptions: {},
}) {
queries = Array.isArray(queries) ? queries : [queries];
filters = Array.isArray(filters) ? filters : [filters];
const validQueries = queries.filter((query) => has(query, 'query'));
const queriesByLanguage = groupBy(validQueries, 'language');
const kueryQuery = buildQueryFromKuery(indexPattern, queriesByLanguage.kuery, allowLeadingWildcards);
const luceneQuery = buildQueryFromLucene(queriesByLanguage.lucene, queryStringOptions);
const kueryQuery = buildQueryFromKuery(indexPattern, queriesByLanguage.kuery, config.allowLeadingWildcards);
const luceneQuery = buildQueryFromLucene(queriesByLanguage.lucene, config.queryStringOptions);
const filterQuery = buildQueryFromFilters(filters, indexPattern);
return {

View file

@ -73,6 +73,7 @@ export function runMochaCli() {
'src/**/__tests__/**/*.js',
'packages/elastic-datemath/test/**/*.js',
'packages/kbn-dev-utils/src/**/__tests__/**/*.js',
'packages/kbn-es-query/src/**/__tests__/**/*.js',
'tasks/**/__tests__/**/*.js',
], {
cwd: resolve(__dirname, '../../..'),

View file

@ -39,11 +39,12 @@ export const tilemap = () => ({
},
fn(context, args) {
const visConfigParams = JSON.parse(args.visConfig);
const { geohash, metric, geocentroid } = visConfigParams.dimensions;
const convertedData = convertToGeoJson(context, {
geohash: visConfigParams.geohash,
metric: visConfigParams.metric,
geocentroid: visConfigParams.geocentroid,
geohash,
metric,
geocentroid,
});
return {

View file

@ -787,7 +787,7 @@ function discoverController(
visualizeHandler.render({
as: 'visualization',
value: {
visType: 'histogram',
visType: $scope.vis.type.name,
visData: resp,
visConfig: $scope.vis.params,
params: {},

View file

@ -26,6 +26,7 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { Schemas } from 'ui/vis/editors/default/schemas';
import tableVisTemplate from './table_vis.html';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { LegacyResponseHandlerProvider as legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy';
import { VisFiltersProvider } from 'ui/vis/vis_filters';
// we need to load the css ourselves
@ -39,6 +40,8 @@ import { VisFiltersProvider } from 'ui/vis/vis_filters';
// register the provider with the visTypes registry
VisTypesRegistryProvider.register(TableVisTypeProvider);
const legacyTableResponseHandler = legacyResponseHandlerProvider().handler;
// define the TableVisType
function TableVisTypeProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
@ -108,6 +111,7 @@ function TableVisTypeProvider(Private) {
}
])
},
responseHandler: legacyTableResponseHandler,
events: {
filterBucket: {
defaultAction: visFilters.filter,

View file

@ -27,6 +27,7 @@ import { Schemas } from 'ui/vis/editors/default/schemas';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { Status } from 'ui/vis/update_status';
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';
import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, courier, config) {
@ -59,6 +60,7 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState,
requiresUpdateStatus: [Status.AGGS, Status.PARAMS, Status.RESIZE, Status.UI_STATE],
requiresPartialRows: true,
visualization: CoordinateMapsVisualization,
responseHandler: convertToGeoJson,
editorConfig: {
collections: {
colorSchemas: Object.values(truncatedColorMaps).map(value => ({ id: value.id, label: value.label })),

View file

@ -17,7 +17,7 @@
* under the License.
*/
export type ResponseHandler = <Response, Data>(response: Response) => Data;
export type ResponseHandler = <Response, Data>(response: Response, dimensions?: any) => Data;
export interface ResponseHandlerDescription {
name: string;

View file

@ -37,7 +37,12 @@ import { visualizationLoader } from './visualization_loader';
import { DataAdapter, RequestAdapter } from '../../inspector/adapters';
import { getTableAggs } from './pipeline_helpers/utilities';
import { VisSavedObject, VisualizeLoaderParams, VisualizeUpdateParams } from './types';
import {
VisResponseData,
VisSavedObject,
VisualizeLoaderParams,
VisualizeUpdateParams,
} from './types';
interface EmbeddedVisualizeHandlerParams extends VisualizeLoaderParams {
Private: IPrivate;
@ -241,22 +246,18 @@ export class EmbeddedVisualizeHandler {
/**
* renders visualization with provided data
* @param visData: visualization data
* @param data: visualization data
*/
public render = (pipelineResponse: any = {}) => {
public render = (data: VisResponseData | null = null) => {
// 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'));
const renderer = renderFunctionsRegistry.get(get(data || {}, 'as', 'visualization'));
if (!renderer) {
return;
}
renderer
.render(
this.element,
pipelineResponse.value || { visType: this.vis.type.name },
this.handlers
)
.render(this.element, get(data, 'value', { visType: this.vis.type.name }), this.handlers)
.then(() => {
if (!this.loaded) {
this.loaded = true;
@ -404,7 +405,7 @@ export class EmbeddedVisualizeHandler {
this.vis.filters = { timeRange: this.dataLoaderParams.timeRange };
return this.dataLoader.fetch(this.dataLoaderParams).then(data => {
if (data.value) {
if (data && data.value) {
this.dataSubject.next(data.value);
}
return data;

View file

@ -26,7 +26,7 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct
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}' "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":0,\\"geohash\\":1,\\"geocentroid\\":3}}' "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles timelion function 1`] = `"timelion_vis expression='foo' interval='bar' "`;

View file

@ -50,11 +50,16 @@ interface Schemas {
}
type buildVisFunction = (visState: VisState, schemas: Schemas) => string;
type buildVisConfigFunction = (visState: Vis, schemas: Schemas) => VisState;
interface BuildPipelineVisFunction {
[key: string]: buildVisFunction;
}
interface BuildVisConfigFunction {
[key: string]: buildVisConfigFunction;
}
const vislibCharts: string[] = [
'area',
'gauge',
@ -214,6 +219,33 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
const visConfig = prepareJson('visConfig', visState.params);
return `kibana_markdown ${expression}${visConfig}`;
},
table: (visState, schemas) => {
const visConfig = buildVisConfig.table(visState, schemas);
return `kibana_table ${prepareJson('visConfig', visConfig)}`;
},
metric: (visState, schemas) => {
const visConfig = buildVisConfig.metric(visState, schemas);
return `kibana_metric ${prepareJson('visConfig', visConfig)}`;
},
tagcloud: (visState, schemas) => {
const visConfig = buildVisConfig.tagcloud(visState, schemas);
return `tagcloud ${prepareJson('visConfig', visConfig)}`;
},
region_map: (visState, schemas) => {
const visConfig = buildVisConfig.region_map(visState, schemas);
return `regionmap ${prepareJson('visConfig', visConfig)}`;
},
tile_map: (visState, schemas) => {
const visConfig = buildVisConfig.tile_map(visState, schemas);
return `tilemap ${prepareJson('visConfig', visConfig)}`;
},
pie: (visState, schemas) => {
const visConfig = buildVisConfig.pie(visState, schemas);
return `kibana_pie ${prepareJson('visConfig', visConfig)}`;
},
};
const buildVisConfig: BuildVisConfigFunction = {
table: (visState, schemas) => {
const visConfig = visState.params;
visConfig.dimensions = {
@ -222,7 +254,7 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
splitRow: schemas.split_row,
splitColumn: schemas.split_column,
};
return `kibana_table ${prepareJson('visConfig', visConfig)}`;
return visConfig;
},
metric: (visState, schemas) => {
const visConfig = visState.params;
@ -230,7 +262,7 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
if (schemas.group) {
visConfig.metric.bucket = schemas.group[0];
}
return `kibana_metric ${prepareJson('visConfig', visConfig)}`;
return visConfig;
},
tagcloud: (visState, schemas) => {
const visConfig = visState.params;
@ -238,7 +270,7 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
if (schemas.segment) {
visConfig.bucket = schemas.segment[0];
}
return `tagcloud ${prepareJson('visConfig', visConfig)}`;
return visConfig;
},
region_map: (visState, schemas) => {
const visConfig = visState.params;
@ -246,14 +278,16 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
if (schemas.segment) {
visConfig.bucket = schemas.segment[0];
}
return `regionmap ${prepareJson('visConfig', visConfig)}`;
return visConfig;
},
tile_map: (visState, schemas) => {
const visConfig = visState.params;
visConfig.metric = schemas.metric[0];
visConfig.geohash = schemas.segment ? schemas.segment[0] : null;
visConfig.geocentroid = schemas.geo_centroid ? schemas.geo_centroid[0] : null;
return `tilemap ${prepareJson('visConfig', visConfig)}`;
visConfig.dimensions = {
metric: schemas.metric[0],
geohash: schemas.segment ? schemas.segment[0] : null,
geocentroid: schemas.geo_centroid ? schemas.geo_centroid[0] : null,
};
return visConfig;
},
pie: (visState, schemas) => {
const visConfig = visState.params;
@ -263,7 +297,7 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
splitRow: schemas.split_row,
splitColumn: schemas.split_column,
};
return `kibana_pie ${prepareJson('visConfig', visConfig)}`;
return visConfig;
},
};
@ -291,6 +325,19 @@ export const buildVislibDimensions = (vis: any, timeRange?: any) => {
return dimensions;
};
// If not using the expression pipeline (i.e. visualize_data_loader), we need a mechanism to
// take a Vis object and decorate it with the necessary params (dimensions, bucket, metric, etc)
export const decorateVisObject = (vis: Vis, params: { timeRange?: any }) => {
const schemas = getSchemas(vis, params.timeRange);
let visConfig = vis.params;
if (buildVisConfig[vis.type.name]) {
visConfig = buildVisConfig[vis.type.name](vis, schemas);
vis.params = visConfig;
} else if (vislibCharts.includes(vis.type.name)) {
visConfig.dimensions = buildVislibDimensions(vis, params.timeRange);
}
};
export const buildPipeline = (
vis: Vis,
params: { searchSource: SearchSource; timeRange?: any }

View file

@ -57,6 +57,18 @@ export interface VisSavedObject {
destroy: () => void;
}
interface VisResponseValue {
visType: string;
visData: object;
visConfig: object;
params?: object;
}
export interface VisResponseData {
as: string;
value: VisResponseValue;
}
/**
* The parameters accepted by the embedVisualize calls.
*/

View file

@ -31,10 +31,13 @@ import {
Vis,
} from '../../vis';
import { VisResponseData } from './types';
// @ts-ignore No typing present
import { isTermSizeZeroError } from '../../elasticsearch_errors';
import { toastNotifications } from 'ui/notify';
import { decorateVisObject } from 'ui/visualize/loader/pipeline_helpers/build_pipeline';
function getHandler<T extends RequestHandler | ResponseHandler>(
from: Array<{ name: string; handler: T }>,
@ -67,11 +70,14 @@ export class VisualizeDataLoader {
this.responseHandler = getHandler(responseHandlers, responseHandler);
}
public async fetch(params: RequestHandlerParams): Promise<any> {
public async fetch(params: RequestHandlerParams): Promise<VisResponseData | void> {
this.vis.filters = { timeRange: params.timeRange };
this.vis.requestError = undefined;
this.vis.showRequestError = false;
// add necessary params to vis object (dimensions, bucket, metric, etc)
decorateVisObject(this.vis, { timeRange: params.timeRange });
try {
// searchSource is only there for courier request handler
const requestHandlerResponse = await this.requestHandler({
@ -96,9 +102,20 @@ export class VisualizeDataLoader {
this.previousRequestHandlerResponse = requestHandlerResponse;
if (!canSkipResponseHandler) {
this.visData = await Promise.resolve(this.responseHandler(requestHandlerResponse));
this.visData = await Promise.resolve(
this.responseHandler(requestHandlerResponse, this.vis.params.dimensions)
);
}
return this.visData;
return {
as: 'visualization',
value: {
visType: this.vis.type.name,
visData: this.visData,
visConfig: this.vis.params,
params: {},
},
};
} catch (error) {
params.searchSource.cancelQueued();
@ -110,11 +127,12 @@ export class VisualizeDataLoader {
console.error(error);
if (isTermSizeZeroError(error)) {
return toastNotifications.addDanger(
toastNotifications.addDanger(
`Your visualization ('${this.vis.title}') has an error: it has a term ` +
`aggregation with a size of 0. Please set it to a number greater than 0 to resolve ` +
`the error.`
);
return;
}
toastNotifications.addDanger({