Use Search API in TSVB (#76274) (#77767)

* Use Search API for TSVB

* Fixed ci

* Fixed ci

* Use constants

* Fixed tests

* Fixed ci

* Fixed ci

* Back old rollup search

* Fixed test

* Fixed tests

* Fixed issue with series data

* Fixed comments

* Fixed comments

* Fixed unit test

* Deleted unused import

* Fixed comments

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Uladzislau Lasitsa <Uladzislau_Lasitsa@epam.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Stratoula Kalafateli 2020-09-18 08:58:35 +03:00 committed by GitHub
parent 1567be984d
commit 5242103e4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 194 additions and 688 deletions

View file

@ -21,7 +21,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/serve
import { VisTypeTimeseriesConfig, config as configSchema } from './config';
import { VisTypeTimeseriesPlugin } from './plugin';
export { VisTypeTimeseriesSetup, Framework } from './plugin';
export { VisTypeTimeseriesSetup } from './plugin';
export const config: PluginConfigDescriptor<VisTypeTimeseriesConfig> = {
deprecations: ({ unused, renameFromRoot }) => [
@ -39,10 +39,10 @@ export const config: PluginConfigDescriptor<VisTypeTimeseriesConfig> = {
export { ValidationTelemetryServiceSetup } from './validation_telemetry';
// @ts-ignore
export { AbstractSearchStrategy } from './lib/search_strategies/strategies/abstract_search_strategy';
// @ts-ignore
export { AbstractSearchRequest } from './lib/search_strategies/search_requests/abstract_request';
export {
AbstractSearchStrategy,
ReqFacade,
} from './lib/search_strategies/strategies/abstract_search_strategy';
// @ts-ignore
export { DefaultSearchCapabilities } from './lib/search_strategies/default_search_capabilities';

View file

@ -38,6 +38,7 @@ export async function getFields(
// level object passed from here. The layers should be refactored fully at some point, but for now
// this works and we are still using the New Platform services for these vis data portions.
const reqFacade: ReqFacade = {
requestContext,
...request,
framework,
payload: {},
@ -48,22 +49,6 @@ export async function getFields(
},
getUiSettingsService: () => requestContext.core.uiSettings.client,
getSavedObjectsClient: () => requestContext.core.savedObjects.client,
server: {
plugins: {
elasticsearch: {
getCluster: () => {
return {
callWithRequest: async (req: any, endpoint: string, params: any) => {
return await requestContext.core.elasticsearch.legacy.client.callAsCurrentUser(
endpoint,
params
);
},
};
},
},
},
},
getEsShardTimeout: async () => {
return await framework.globalConfig$
.pipe(

View file

@ -21,7 +21,7 @@ import { FakeRequest, RequestHandlerContext } from 'kibana/server';
import _ from 'lodash';
import { first, map } from 'rxjs/operators';
import { getPanelData } from './vis_data/get_panel_data';
import { Framework } from '../index';
import { Framework } from '../plugin';
import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy';
interface GetVisDataResponse {
@ -65,28 +65,13 @@ export function getVisData(
// level object passed from here. The layers should be refactored fully at some point, but for now
// this works and we are still using the New Platform services for these vis data portions.
const reqFacade: ReqFacade = {
requestContext,
...request,
framework,
pre: {},
payload: request.body,
getUiSettingsService: () => requestContext.core.uiSettings.client,
getSavedObjectsClient: () => requestContext.core.savedObjects.client,
server: {
plugins: {
elasticsearch: {
getCluster: () => {
return {
callWithRequest: async (req: any, endpoint: string, params: any) => {
return await requestContext.core.elasticsearch.legacy.client.callAsCurrentUser(
endpoint,
params
);
},
};
},
},
},
},
getEsShardTimeout: async () => {
return await framework.globalConfig$
.pipe(

View file

@ -1,28 +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.
*/
export class AbstractSearchRequest {
constructor(req, callWithRequest) {
this.req = req;
this.callWithRequest = callWithRequest;
}
search() {
throw new Error('AbstractSearchRequest: search method should be defined');
}
}

View file

@ -1,46 +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 { AbstractSearchRequest } from './abstract_request';
describe('AbstractSearchRequest', () => {
let searchRequest;
let req;
let callWithRequest;
beforeEach(() => {
req = {};
callWithRequest = jest.fn();
searchRequest = new AbstractSearchRequest(req, callWithRequest);
});
test('should init an AbstractSearchRequest instance', () => {
expect(searchRequest.req).toBe(req);
expect(searchRequest.callWithRequest).toBe(callWithRequest);
expect(searchRequest.search).toBeDefined();
});
test('should throw an error trying to search', () => {
try {
searchRequest.search();
} catch (error) {
expect(error instanceof Error).toBe(true);
expect(error.message).toEqual('AbstractSearchRequest: search method should be defined');
}
});
});

View file

@ -1,49 +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 { AbstractSearchRequest } from './abstract_request';
import { UI_SETTINGS } from '../../../../../data/server';
const SEARCH_METHOD = 'msearch';
export class MultiSearchRequest extends AbstractSearchRequest {
async search(searches) {
const includeFrozen = await this.req
.getUiSettingsService()
.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
const multiSearchBody = searches.reduce(
(acc, { body, index }) => [
...acc,
{
index,
ignoreUnavailable: true,
},
body,
],
[]
);
const { responses } = await this.callWithRequest(this.req, SEARCH_METHOD, {
body: multiSearchBody,
rest_total_hits_as_int: true,
ignore_throttled: !includeFrozen,
});
return responses;
}
}

View file

@ -1,67 +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 { MultiSearchRequest } from './multi_search_request';
import { UI_SETTINGS } from '../../../../../data/server';
describe('MultiSearchRequest', () => {
let searchRequest;
let req;
let callWithRequest;
let getServiceMock;
let includeFrozen;
beforeEach(() => {
includeFrozen = false;
getServiceMock = jest.fn().mockResolvedValue(includeFrozen);
req = {
getUiSettingsService: jest.fn().mockReturnValue({ get: getServiceMock }),
};
callWithRequest = jest.fn().mockReturnValue({ responses: [] });
searchRequest = new MultiSearchRequest(req, callWithRequest);
});
test('should init an MultiSearchRequest instance', () => {
expect(searchRequest.req).toBe(req);
expect(searchRequest.callWithRequest).toBe(callWithRequest);
expect(searchRequest.search).toBeDefined();
});
test('should get the response from elastic msearch', async () => {
const searches = [
{ body: 'body1', index: 'index' },
{ body: 'body2', index: 'index' },
];
const responses = await searchRequest.search(searches);
expect(responses).toEqual([]);
expect(req.getUiSettingsService).toHaveBeenCalled();
expect(getServiceMock).toHaveBeenCalledWith(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
expect(callWithRequest).toHaveBeenCalledWith(req, 'msearch', {
body: [
{ ignoreUnavailable: true, index: 'index' },
'body1',
{ ignoreUnavailable: true, index: 'index' },
'body2',
],
rest_total_hits_as_int: true,
ignore_throttled: !includeFrozen,
});
});
});

View file

@ -1,37 +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 { AbstractSearchRequest } from './abstract_request';
import { MultiSearchRequest } from './multi_search_request';
import { SingleSearchRequest } from './single_search_request';
export class SearchRequest extends AbstractSearchRequest {
getSearchRequestType(searches) {
const isMultiSearch = Array.isArray(searches) && searches.length > 1;
const SearchRequest = isMultiSearch ? MultiSearchRequest : SingleSearchRequest;
return new SearchRequest(this.req, this.callWithRequest);
}
async search(options) {
const concreteSearchRequest = this.getSearchRequestType(options);
return concreteSearchRequest.search(options);
}
}

View file

@ -1,76 +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 { SearchRequest } from './search_request';
import { MultiSearchRequest } from './multi_search_request';
import { SingleSearchRequest } from './single_search_request';
describe('SearchRequest', () => {
let searchRequest;
let req;
let callWithRequest;
let getServiceMock;
let includeFrozen;
beforeEach(() => {
includeFrozen = false;
getServiceMock = jest.fn().mockResolvedValue(includeFrozen);
req = {
getUiSettingsService: jest.fn().mockReturnValue({ get: getServiceMock }),
};
callWithRequest = jest.fn().mockReturnValue({ responses: [] });
searchRequest = new SearchRequest(req, callWithRequest);
});
test('should init an AbstractSearchRequest instance', () => {
expect(searchRequest.req).toBe(req);
expect(searchRequest.callWithRequest).toBe(callWithRequest);
expect(searchRequest.search).toBeDefined();
});
test('should return search value', async () => {
const concreteSearchRequest = {
search: jest.fn().mockReturnValue('concreteSearchRequest'),
};
const options = {};
searchRequest.getSearchRequestType = jest.fn().mockReturnValue(concreteSearchRequest);
const result = await searchRequest.search(options);
expect(result).toBe('concreteSearchRequest');
});
test('should return a MultiSearchRequest for multi searches', () => {
const searches = [
{ index: 'index', body: 'body' },
{ index: 'index', body: 'body' },
];
const result = searchRequest.getSearchRequestType(searches);
expect(result instanceof MultiSearchRequest).toBe(true);
});
test('should return a SingleSearchRequest for single search', () => {
const searches = [{ index: 'index', body: 'body' }];
const result = searchRequest.getSearchRequestType(searches);
expect(result instanceof SingleSearchRequest).toBe(true);
});
});

View file

@ -1,37 +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 { AbstractSearchRequest } from './abstract_request';
import { UI_SETTINGS } from '../../../../../data/server';
const SEARCH_METHOD = 'search';
export class SingleSearchRequest extends AbstractSearchRequest {
async search([{ body, index }]) {
const includeFrozen = await this.req
.getUiSettingsService()
.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
const resp = await this.callWithRequest(this.req, SEARCH_METHOD, {
ignore_throttled: !includeFrozen,
body,
index,
});
return [resp];
}
}

View file

@ -1,59 +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 { SingleSearchRequest } from './single_search_request';
import { UI_SETTINGS } from '../../../../../data/server';
describe('SingleSearchRequest', () => {
let searchRequest;
let req;
let callWithRequest;
let getServiceMock;
let includeFrozen;
beforeEach(() => {
includeFrozen = false;
getServiceMock = jest.fn().mockResolvedValue(includeFrozen);
req = {
getUiSettingsService: jest.fn().mockReturnValue({ get: getServiceMock }),
};
callWithRequest = jest.fn().mockReturnValue({});
searchRequest = new SingleSearchRequest(req, callWithRequest);
});
test('should init an SingleSearchRequest instance', () => {
expect(searchRequest.req).toBe(req);
expect(searchRequest.callWithRequest).toBe(callWithRequest);
expect(searchRequest.search).toBeDefined();
});
test('should get the response from elastic search', async () => {
const searches = [{ body: 'body', index: 'index' }];
const responses = await searchRequest.search(searches);
expect(responses).toEqual([{}]);
expect(req.getUiSettingsService).toHaveBeenCalled();
expect(getServiceMock).toHaveBeenCalledWith(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
expect(callWithRequest).toHaveBeenCalledWith(req, 'search', {
body: 'body',
index: 'index',
ignore_throttled: !includeFrozen,
});
});
});

View file

@ -65,7 +65,7 @@ describe('SearchStrategyRegister', () => {
});
test('should add a strategy if it is an instance of AbstractSearchStrategy', () => {
const anotherSearchStrategy = new MockSearchStrategy({}, {} as any, {});
const anotherSearchStrategy = new MockSearchStrategy('es');
const addedStrategies = registry.addStrategy(anotherSearchStrategy);
expect(addedStrategies.length).toEqual(2);
@ -75,7 +75,7 @@ describe('SearchStrategyRegister', () => {
test('should return a MockSearchStrategy instance', async () => {
const req = {};
const indexPattern = '*';
const anotherSearchStrategy = new MockSearchStrategy({}, {} as any, {});
const anotherSearchStrategy = new MockSearchStrategy('es');
registry.addStrategy(anotherSearchStrategy);
const { searchStrategy, capabilities } = (await registry.getViableStrategy(req, indexPattern))!;

View file

@ -18,24 +18,13 @@
*/
import { AbstractSearchStrategy } from './abstract_search_strategy';
class SearchRequest {
constructor(req, callWithRequest) {
this.req = req;
this.callWithRequest = callWithRequest;
}
}
describe('AbstractSearchStrategy', () => {
let abstractSearchStrategy;
let server;
let callWithRequestFactory;
let req;
let mockedFields;
let indexPattern;
beforeEach(() => {
server = {};
callWithRequestFactory = jest.fn().mockReturnValue('callWithRequest');
mockedFields = {};
req = {
pre: {
@ -45,16 +34,11 @@ describe('AbstractSearchStrategy', () => {
},
};
abstractSearchStrategy = new AbstractSearchStrategy(
server,
callWithRequestFactory,
SearchRequest
);
abstractSearchStrategy = new AbstractSearchStrategy('es');
});
test('should init an AbstractSearchStrategy instance', () => {
expect(abstractSearchStrategy.getCallWithRequestInstance).toBeDefined();
expect(abstractSearchStrategy.getSearchRequest).toBeDefined();
expect(abstractSearchStrategy.search).toBeDefined();
expect(abstractSearchStrategy.getFieldsForWildcard).toBeDefined();
expect(abstractSearchStrategy.checkForViability).toBeDefined();
});
@ -68,17 +52,46 @@ describe('AbstractSearchStrategy', () => {
});
});
test('should invoke callWithRequestFactory with req param passed', () => {
abstractSearchStrategy.getCallWithRequestInstance(req);
test('should return response', async () => {
const searches = [{ body: 'body', index: 'index' }];
const searchFn = jest.fn().mockReturnValue(Promise.resolve({}));
expect(callWithRequestFactory).toHaveBeenCalledWith(server, req);
});
const responses = await abstractSearchStrategy.search(
{
requestContext: {},
framework: {
core: {
getStartServices: jest.fn().mockReturnValue(
Promise.resolve([
{},
{
data: {
search: {
search: searchFn,
},
},
},
])
),
},
},
},
searches
);
test('should return a search request', () => {
const searchRequest = abstractSearchStrategy.getSearchRequest(req);
expect(searchRequest instanceof SearchRequest).toBe(true);
expect(searchRequest.callWithRequest).toBe('callWithRequest');
expect(searchRequest.req).toBe(req);
expect(responses).toEqual([{}]);
expect(searchFn).toHaveBeenCalledWith(
{},
{
params: {
body: 'body',
index: 'index',
},
indexType: undefined,
},
{
strategy: 'es',
}
);
});
});

View file

@ -18,7 +18,7 @@
*/
import {
LegacyAPICaller,
RequestHandlerContext,
FakeRequest,
IUiSettingsClient,
SavedObjectsClientContract,
@ -33,6 +33,7 @@ import { IndexPatternsFetcher } from '../../../../../data/server';
* This will be replaced by standard KibanaRequest and RequestContext objects in a later version.
*/
export type ReqFacade = FakeRequest & {
requestContext: RequestHandlerContext;
framework: Framework;
payload: unknown;
pre: {
@ -40,34 +41,42 @@ export type ReqFacade = FakeRequest & {
};
getUiSettingsService: () => IUiSettingsClient;
getSavedObjectsClient: () => SavedObjectsClientContract;
server: {
plugins: {
elasticsearch: {
getCluster: () => {
callWithRequest: (req: ReqFacade, endpoint: string, params: any) => Promise<any>;
};
};
};
};
getEsShardTimeout: () => Promise<number>;
};
export class AbstractSearchStrategy {
public getCallWithRequestInstance: (req: ReqFacade) => LegacyAPICaller;
public getSearchRequest: (req: ReqFacade) => any;
public searchStrategyName!: string;
public indexType?: string;
public additionalParams: any;
constructor(
server: any,
callWithRequestFactory: (server: any, req: ReqFacade) => LegacyAPICaller,
SearchRequest: any
) {
this.getCallWithRequestInstance = (req) => callWithRequestFactory(server, req);
constructor(name: string, type?: string, additionalParams: any = {}) {
this.searchStrategyName = name;
this.indexType = type;
this.additionalParams = additionalParams;
}
this.getSearchRequest = (req) => {
const callWithRequest = this.getCallWithRequestInstance(req);
return new SearchRequest(req, callWithRequest);
};
async search(req: ReqFacade, bodies: any[], options = {}) {
const [, deps] = await req.framework.core.getStartServices();
const requests: any[] = [];
bodies.forEach((body) => {
requests.push(
deps.data.search.search(
req.requestContext,
{
params: {
...body,
...this.additionalParams,
},
indexType: this.indexType,
},
{
...options,
strategy: this.searchStrategyName,
}
)
);
});
return Promise.all(requests);
}
async getFieldsForWildcard(req: ReqFacade, indexPattern: string, capabilities: any) {

View file

@ -16,21 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ES_SEARCH_STRATEGY } from '../../../../../data/server';
import { AbstractSearchStrategy } from './abstract_search_strategy';
import { SearchRequest } from '../search_requests/search_request';
import { DefaultSearchCapabilities } from '../default_search_capabilities';
const callWithRequestFactory = (server, request) => {
const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data');
return callWithRequest;
};
export class DefaultSearchStrategy extends AbstractSearchStrategy {
name = 'default';
constructor(server) {
super(server, callWithRequestFactory, SearchRequest);
constructor() {
super(ES_SEARCH_STRATEGY);
}
checkForViability(req) {

View file

@ -20,42 +20,20 @@ import { DefaultSearchStrategy } from './default_search_strategy';
describe('DefaultSearchStrategy', () => {
let defaultSearchStrategy;
let server;
let callWithRequest;
let req;
beforeEach(() => {
server = {};
callWithRequest = jest.fn();
req = {
server: {
plugins: {
elasticsearch: {
getCluster: jest.fn().mockReturnValue({
callWithRequest,
}),
},
},
},
};
defaultSearchStrategy = new DefaultSearchStrategy(server);
req = {};
defaultSearchStrategy = new DefaultSearchStrategy();
});
test('should init an DefaultSearchStrategy instance', () => {
expect(defaultSearchStrategy.name).toBe('default');
expect(defaultSearchStrategy.checkForViability).toBeDefined();
expect(defaultSearchStrategy.getCallWithRequestInstance).toBeDefined();
expect(defaultSearchStrategy.getSearchRequest).toBeDefined();
expect(defaultSearchStrategy.search).toBeDefined();
expect(defaultSearchStrategy.getFieldsForWildcard).toBeDefined();
});
test('should invoke callWithRequestFactory with passed params', () => {
const value = defaultSearchStrategy.getCallWithRequestInstance(req);
expect(value).toBe(callWithRequest);
expect(req.server.plugins.elasticsearch.getCluster).toHaveBeenCalledWith('data');
});
test('should check a strategy for viability', () => {
const value = defaultSearchStrategy.checkForViability(req);

View file

@ -39,7 +39,6 @@ export async function getAnnotations({
capabilities,
series,
}) {
const searchRequest = searchStrategy.getSearchRequest(req);
const annotations = panel.annotations.filter(validAnnotation);
const lastSeriesTimestamp = getLastSeriesTimestamp(series);
const handleAnnotationResponseBy = handleAnnotationResponse(lastSeriesTimestamp);
@ -47,6 +46,7 @@ export async function getAnnotations({
const bodiesPromises = annotations.map((annotation) =>
getAnnotationRequestParams(req, panel, annotation, esQueryConfig, capabilities)
);
const searches = (await Promise.all(bodiesPromises)).reduce(
(acc, items) => acc.concat(items),
[]
@ -55,10 +55,10 @@ export async function getAnnotations({
if (!searches.length) return { responses: [] };
try {
const data = await searchRequest.search(searches);
const data = await searchStrategy.search(req.framework.core, req.requestContext, searches);
return annotations.reduce((acc, annotation, index) => {
acc[annotation.id] = handleAnnotationResponseBy(data[index], annotation);
acc[annotation.id] = handleAnnotationResponseBy(data[index].rawResponse, annotation);
return acc;
}, {});

View file

@ -28,7 +28,6 @@ export async function getSeriesData(req, panel) {
searchStrategy,
capabilities,
} = await req.framework.searchStrategyRegistry.getViableStrategyForPanel(req, panel);
const searchRequest = searchStrategy.getSearchRequest(req);
const esQueryConfig = await getEsQueryConfig(req);
const meta = {
type: panel.type,
@ -45,8 +44,13 @@ export async function getSeriesData(req, panel) {
[]
);
const data = await searchRequest.search(searches);
const series = data.map(handleResponseBody(panel));
const data = await searchStrategy.search(req, searches);
const handleResponseBodyFn = handleResponseBody(panel);
const series = data.map((resp) =>
handleResponseBodyFn(resp.rawResponse ? resp.rawResponse : resp)
);
let annotations = null;
if (panel.annotations && panel.annotations.length) {

View file

@ -30,7 +30,6 @@ export async function getTableData(req, panel) {
searchStrategy,
capabilities,
} = await req.framework.searchStrategyRegistry.getViableStrategy(req, panelIndexPattern);
const searchRequest = searchStrategy.getSearchRequest(req);
const esQueryConfig = await getEsQueryConfig(req);
const { indexPatternObject } = await getIndexPatternObject(req, panelIndexPattern);
@ -41,13 +40,18 @@ export async function getTableData(req, panel) {
try {
const body = buildRequestBody(req, panel, esQueryConfig, indexPatternObject, capabilities);
const [resp] = await searchRequest.search([
const [resp] = await searchStrategy.search(req, [
{
body,
index: panelIndexPattern,
},
]);
const buckets = get(resp, 'aggregations.pivot.buckets', []);
const buckets = get(
resp.rawResponse ? resp.rawResponse : resp,
'aggregations.pivot.buckets',
[]
);
return {
...meta,

View file

@ -33,6 +33,7 @@ import { VisTypeTimeseriesConfig } from './config';
import { getVisData, GetVisData, GetVisDataOptions } from './lib/get_vis_data';
import { ValidationTelemetryService } from './validation_telemetry';
import { UsageCollectionSetup } from '../../usage_collection/server';
import { PluginStart } from '../../data/server';
import { visDataRoutes } from './routes/vis';
// @ts-ignore
import { fieldsRoutes } from './routes/fields';
@ -47,6 +48,10 @@ interface VisTypeTimeseriesPluginSetupDependencies {
usageCollection?: UsageCollectionSetup;
}
interface VisTypeTimeseriesPluginStartDependencies {
data: PluginStart;
}
export interface VisTypeTimeseriesSetup {
getVisData: (
requestContext: RequestHandlerContext,
@ -57,7 +62,7 @@ export interface VisTypeTimeseriesSetup {
}
export interface Framework {
core: CoreSetup;
core: CoreSetup<VisTypeTimeseriesPluginStartDependencies>;
plugins: any;
config$: Observable<VisTypeTimeseriesConfig>;
globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$'];
@ -74,7 +79,10 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
this.validationTelementryService = new ValidationTelemetryService();
}
public setup(core: CoreSetup, plugins: VisTypeTimeseriesPluginSetupDependencies) {
public setup(
core: CoreSetup<VisTypeTimeseriesPluginStartDependencies>,
plugins: VisTypeTimeseriesPluginSetupDependencies
) {
const logger = this.initializerContext.logger.get('visTypeTimeseries');
core.uiSettings.register(uiSettings);
const config$ = this.initializerContext.config.create<VisTypeTimeseriesConfig>();

View file

@ -21,7 +21,8 @@ import { IRouter, KibanaRequest } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { getVisData, GetVisDataOptions } from '../lib/get_vis_data';
import { visPayloadSchema } from '../../common/vis_schema';
import { Framework, ValidationTelemetryServiceSetup } from '../index';
import { ValidationTelemetryServiceSetup } from '../index';
import { Framework } from '../plugin';
const escapeHatch = schema.object({}, { unknowns: 'allow' });

View file

@ -11,4 +11,6 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new EnhancedDataServerPlugin(initializerContext);
}
export { ENHANCED_ES_SEARCH_STRATEGY } from '../common';
export { EnhancedDataServerPlugin as Plugin };

View file

@ -6,21 +6,16 @@
import { registerRollupSearchStrategy } from './register_rollup_search_strategy';
describe('Register Rollup Search Strategy', () => {
let routeDependencies;
let addSearchStrategy;
let getRollupService;
beforeEach(() => {
routeDependencies = {
router: jest.fn().mockName('router'),
elasticsearchService: jest.fn().mockName('elasticsearchService'),
elasticsearch: jest.fn().mockName('elasticsearch'),
};
addSearchStrategy = jest.fn().mockName('addSearchStrategy');
getRollupService = jest.fn().mockName('getRollupService');
});
test('should run initialization', () => {
registerRollupSearchStrategy(routeDependencies, addSearchStrategy);
registerRollupSearchStrategy(addSearchStrategy, getRollupService);
expect(addSearchStrategy).toHaveBeenCalled();
});

View file

@ -4,27 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ILegacyScopedClusterClient } from 'src/core/server';
import {
AbstractSearchRequest,
DefaultSearchCapabilities,
AbstractSearchStrategy,
ReqFacade,
} from '../../../../../../src/plugins/vis_type_timeseries/server';
import { CallWithRequestFactoryShim } from '../../types';
import { getRollupSearchStrategy } from './rollup_search_strategy';
import { getRollupSearchRequest } from './rollup_search_request';
import { getRollupSearchCapabilities } from './rollup_search_capabilities';
export const registerRollupSearchStrategy = (
callWithRequestFactory: CallWithRequestFactoryShim,
addSearchStrategy: (searchStrategy: any) => void
addSearchStrategy: (searchStrategy: any) => void,
getRollupService: (reg: ReqFacade) => Promise<ILegacyScopedClusterClient>
) => {
const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest);
const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities);
const RollupSearchStrategy = getRollupSearchStrategy(
AbstractSearchStrategy,
RollupSearchRequest,
RollupSearchCapabilities,
callWithRequestFactory
getRollupService
);
addSearchStrategy(new RollupSearchStrategy());

View file

@ -1,53 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { getRollupSearchRequest } from './rollup_search_request';
class AbstractSearchRequest {
indexPattern = 'indexPattern';
callWithRequest = jest.fn(({ body }) => Promise.resolve(body));
}
describe('Rollup search request', () => {
let RollupSearchRequest;
beforeEach(() => {
RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest);
});
test('should create instance of RollupSearchRequest', () => {
const rollupSearchRequest = new RollupSearchRequest();
expect(rollupSearchRequest).toBeInstanceOf(AbstractSearchRequest);
expect(rollupSearchRequest.search).toBeDefined();
expect(rollupSearchRequest.callWithRequest).toBeDefined();
});
test('should send one request for single search', async () => {
const rollupSearchRequest = new RollupSearchRequest();
const searches = [{ body: 'body', index: 'index' }];
await rollupSearchRequest.search(searches);
expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledTimes(1);
expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledWith('rollup.search', {
body: 'body',
index: 'index',
rest_total_hits_as_int: true,
});
});
test('should send multiple request for multi search', async () => {
const rollupSearchRequest = new RollupSearchRequest();
const searches = [
{ body: 'body', index: 'index' },
{ body: 'body1', index: 'index' },
];
await rollupSearchRequest.search(searches);
expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledTimes(2);
});
});

View file

@ -1,28 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
const SEARCH_METHOD = 'rollup.search';
interface Search {
index: string;
body: {
[key: string]: any;
};
}
export const getRollupSearchRequest = (AbstractSearchRequest: any) =>
class RollupSearchRequest extends AbstractSearchRequest {
async search(searches: Search[]) {
const requests = searches.map(({ body, index }) =>
this.callWithRequest(SEARCH_METHOD, {
body,
index,
rest_total_hits_as_int: true,
})
);
return await Promise.all(requests);
}
};

View file

@ -7,13 +7,32 @@ import { getRollupSearchStrategy } from './rollup_search_strategy';
describe('Rollup Search Strategy', () => {
let RollupSearchStrategy;
let RollupSearchRequest;
let RollupSearchCapabilities;
let callWithRequest;
let rollupResolvedData;
const server = 'server';
const request = 'request';
const request = {
requestContext: {
core: {
elasticsearch: {
client: {
asCurrentUser: {
rollup: {
getRollupIndexCaps: jest.fn().mockImplementation(() => rollupResolvedData),
},
},
},
},
},
},
};
const getRollupService = jest.fn().mockImplementation(() => {
return {
callAsCurrentUser: async () => {
return rollupResolvedData;
},
};
});
const indexPattern = 'indexPattern';
beforeEach(() => {
@ -33,19 +52,17 @@ describe('Rollup Search Strategy', () => {
}
}
RollupSearchRequest = jest.fn();
RollupSearchCapabilities = jest.fn(() => 'capabilities');
callWithRequest = jest.fn().mockImplementation(() => rollupResolvedData);
RollupSearchStrategy = getRollupSearchStrategy(
AbstractSearchStrategy,
RollupSearchRequest,
RollupSearchCapabilities
RollupSearchCapabilities,
getRollupService
);
});
test('should create instance of RollupSearchRequest', () => {
const rollupSearchStrategy = new RollupSearchStrategy(server);
const rollupSearchStrategy = new RollupSearchStrategy();
expect(rollupSearchStrategy.name).toBe('rollup');
});
@ -55,7 +72,7 @@ describe('Rollup Search Strategy', () => {
const rollupIndex = 'rollupIndex';
beforeEach(() => {
rollupSearchStrategy = new RollupSearchStrategy(server);
rollupSearchStrategy = new RollupSearchStrategy();
rollupSearchStrategy.getRollupData = jest.fn(() => ({
[rollupIndex]: {
rollup_jobs: [
@ -104,7 +121,7 @@ describe('Rollup Search Strategy', () => {
let rollupSearchStrategy;
beforeEach(() => {
rollupSearchStrategy = new RollupSearchStrategy(server);
rollupSearchStrategy = new RollupSearchStrategy();
});
test('should return rollup data', async () => {
@ -112,10 +129,7 @@ describe('Rollup Search Strategy', () => {
const rollupData = await rollupSearchStrategy.getRollupData(request, indexPattern);
expect(callWithRequest).toHaveBeenCalledWith('rollup.rollupIndexCapabilities', {
indexPattern,
});
expect(rollupSearchStrategy.getCallWithRequestInstance).toHaveBeenCalledWith(request);
expect(getRollupService).toHaveBeenCalled();
expect(rollupData).toBe('data');
});
@ -135,7 +149,7 @@ describe('Rollup Search Strategy', () => {
const rollupIndex = 'rollupIndex';
beforeEach(() => {
rollupSearchStrategy = new RollupSearchStrategy(server);
rollupSearchStrategy = new RollupSearchStrategy();
fieldsCapabilities = {
[rollupIndex]: {
aggs: {

View file

@ -4,15 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { keyBy, isString } from 'lodash';
import { KibanaRequest } from 'src/core/server';
import { CallWithRequestFactoryShim } from '../../types';
import { ILegacyScopedClusterClient } from 'src/core/server';
import { ReqFacade } from '../../../../../../src/plugins/vis_type_timeseries/server';
import { ENHANCED_ES_SEARCH_STRATEGY } from '../../../../data_enhanced/server';
import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields';
import { getCapabilitiesForRollupIndices } from '../map_capabilities';
const ROLLUP_INDEX_CAPABILITIES_METHOD = 'rollup.rollupIndexCapabilities';
const getRollupIndices = (rollupData: { [key: string]: any[] }) => Object.keys(rollupData);
const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData);
const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*');
const isIndexPatternValid = (indexPattern: string) =>
@ -20,28 +18,40 @@ const isIndexPatternValid = (indexPattern: string) =>
export const getRollupSearchStrategy = (
AbstractSearchStrategy: any,
RollupSearchRequest: any,
RollupSearchCapabilities: any,
callWithRequestFactory: CallWithRequestFactoryShim
getRollupService: (reg: ReqFacade) => Promise<ILegacyScopedClusterClient>
) =>
class RollupSearchStrategy extends AbstractSearchStrategy {
name = 'rollup';
constructor() {
// TODO: When vis_type_timeseries and AbstractSearchStrategy are migrated to the NP, it
// shouldn't require elasticsearchService to be injected, and we can remove this null argument.
super(null, callWithRequestFactory, RollupSearchRequest);
super(ENHANCED_ES_SEARCH_STRATEGY, 'rollup', { rest_total_hits_as_int: true });
}
getRollupData(req: KibanaRequest, indexPattern: string) {
const callWithRequest = this.getCallWithRequestInstance(req);
return callWithRequest(ROLLUP_INDEX_CAPABILITIES_METHOD, {
indexPattern,
}).catch(() => Promise.resolve({}));
async search(req: ReqFacade, bodies: any[], options = {}) {
const rollupService = await getRollupService(req);
const requests: any[] = [];
bodies.forEach((body) => {
requests.push(
rollupService.callAsCurrentUser('rollup.search', {
...body,
rest_total_hits_as_int: true,
})
);
});
return Promise.all(requests);
}
async checkForViability(req: KibanaRequest, indexPattern: string) {
async getRollupData(req: ReqFacade, indexPattern: string) {
const rollupService = await getRollupService(req);
return rollupService
.callAsCurrentUser('rollup.rollupIndexCapabilities', {
indexPattern,
})
.catch(() => Promise.resolve({}));
}
async checkForViability(req: ReqFacade, indexPattern: string) {
let isViable = false;
let capabilities = null;
@ -66,7 +76,7 @@ export const getRollupSearchStrategy = (
}
async getFieldsForWildcard(
req: KibanaRequest,
req: ReqFacade,
indexPattern: string,
{
fieldsCapabilities,

View file

@ -17,17 +17,16 @@ import {
ILegacyCustomClusterClient,
Plugin,
Logger,
KibanaRequest,
PluginInitializerContext,
ILegacyScopedClusterClient,
LegacyAPICaller,
SharedGlobalConfig,
} from 'src/core/server';
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { ReqFacade } from '../../../../src/plugins/vis_type_timeseries/server';
import { PLUGIN, CONFIG_ROLLUPS } from '../common';
import { Dependencies, CallWithRequestFactoryShim } from './types';
import { Dependencies } from './types';
import { registerApiRoutes } from './routes';
import { License } from './services';
import { registerRollupUsageCollector } from './collectors';
@ -132,19 +131,12 @@ export class RollupPlugin implements Plugin<void, void, any, any> {
});
if (visTypeTimeseries) {
// TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim.
const callWithRequestFactoryShim = (
elasticsearchServiceShim: CallWithRequestFactoryShim,
request: KibanaRequest
): LegacyAPICaller => {
return async (...args: Parameters<LegacyAPICaller>) => {
this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices));
return await this.rollupEsClient.asScoped(request).callAsCurrentUser(...args);
};
const getRollupService = async (request: ReqFacade) => {
this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices));
return this.rollupEsClient.asScoped(request);
};
const { addSearchStrategy } = visTypeTimeseries;
registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy);
registerRollupSearchStrategy(addSearchStrategy, getRollupService);
}
if (usageCollection) {

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { IRouter, LegacyAPICaller, KibanaRequest } from 'src/core/server';
import { IRouter } from 'src/core/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server';
@ -39,9 +39,3 @@ export interface RouteDependencies {
IndexPatternsFetcher: typeof IndexPatternsFetcher;
};
}
// TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim.
export type CallWithRequestFactoryShim = (
elasticsearchServiceShim: CallWithRequestFactoryShim,
request: KibanaRequest
) => LegacyAPICaller;