mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[index_pattern_management]: Replace calls to /elasticsearch/_msearch
with internal route. (#77564)
This commit is contained in:
parent
23a0dcebe2
commit
ba53369170
9 changed files with 361 additions and 83 deletions
|
@ -81,7 +81,7 @@ export class TestScript extends Component<TestScriptProps, TestScriptState> {
|
|||
}
|
||||
|
||||
previewScript = async (searchContext?: { query?: Query | undefined }) => {
|
||||
const { indexPattern, lang, name, script, executeScript } = this.props;
|
||||
const { indexPattern, name, script, executeScript } = this.props;
|
||||
|
||||
if (!script || script.length === 0) {
|
||||
return;
|
||||
|
@ -104,7 +104,6 @@ export class TestScript extends Component<TestScriptProps, TestScriptState> {
|
|||
|
||||
const scriptResponse = await executeScript({
|
||||
name: name as string,
|
||||
lang,
|
||||
script,
|
||||
indexPatternTitle: indexPattern.title,
|
||||
query,
|
||||
|
@ -122,7 +121,7 @@ export class TestScript extends Component<TestScriptProps, TestScriptState> {
|
|||
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
previewData: scriptResponse.hits.hits.map((hit: any) => ({
|
||||
previewData: scriptResponse.hits?.hits.map((hit: any) => ({
|
||||
_id: hit._id,
|
||||
...hit._source,
|
||||
...hit.fields,
|
||||
|
|
|
@ -784,7 +784,6 @@ export class FieldEditor extends PureComponent<FieldEdiorProps, FieldEditorState
|
|||
|
||||
const isValid = await isScriptValid({
|
||||
name: field.name,
|
||||
lang: field.lang as string,
|
||||
script: field.script as string,
|
||||
indexPatternTitle: indexPattern.title,
|
||||
http: this.context.services.http,
|
||||
|
|
|
@ -17,72 +17,50 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Query } from 'src/plugins/data/public';
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { ExecuteScriptParams, ExecuteScriptResult } from '../types';
|
||||
|
||||
export const executeScript = async ({
|
||||
name,
|
||||
lang,
|
||||
script,
|
||||
indexPatternTitle,
|
||||
query,
|
||||
additionalFields = [],
|
||||
http,
|
||||
}: ExecuteScriptParams): Promise<ExecuteScriptResult> => {
|
||||
// Using _msearch because _search with index name in path dorks everything up
|
||||
const header = {
|
||||
index: indexPatternTitle,
|
||||
ignore_unavailable: true,
|
||||
};
|
||||
|
||||
const search = {
|
||||
query: {
|
||||
match_all: {},
|
||||
} as Query['query'],
|
||||
script_fields: {
|
||||
[name]: {
|
||||
script: {
|
||||
lang,
|
||||
source: script,
|
||||
},
|
||||
},
|
||||
},
|
||||
_source: undefined as string[] | undefined,
|
||||
size: 10,
|
||||
timeout: '30s',
|
||||
};
|
||||
|
||||
if (additionalFields.length > 0) {
|
||||
search._source = additionalFields;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
search.query = query;
|
||||
}
|
||||
|
||||
const body = `${JSON.stringify(header)}\n${JSON.stringify(search)}\n`;
|
||||
const esResp = await http.fetch('/elasticsearch/_msearch', { method: 'POST', body });
|
||||
// unwrap _msearch response
|
||||
return esResp.responses[0];
|
||||
return http
|
||||
.post('/internal/index-pattern-management/preview_scripted_field', {
|
||||
body: JSON.stringify({
|
||||
index: indexPatternTitle,
|
||||
name,
|
||||
script,
|
||||
query,
|
||||
additionalFields,
|
||||
}),
|
||||
})
|
||||
.then((res) => ({
|
||||
status: res.statusCode,
|
||||
hits: res.body.hits,
|
||||
}))
|
||||
.catch((err) => ({
|
||||
status: err.statusCode,
|
||||
error: err.body.attributes.error,
|
||||
}));
|
||||
};
|
||||
|
||||
export const isScriptValid = async ({
|
||||
name,
|
||||
lang,
|
||||
script,
|
||||
indexPatternTitle,
|
||||
http,
|
||||
}: {
|
||||
name: string;
|
||||
lang: string;
|
||||
script: string;
|
||||
indexPatternTitle: string;
|
||||
http: HttpStart;
|
||||
}) => {
|
||||
const scriptResponse = await executeScript({
|
||||
name,
|
||||
lang,
|
||||
script,
|
||||
indexPatternTitle,
|
||||
http,
|
||||
|
|
|
@ -28,7 +28,6 @@ export interface Sample {
|
|||
|
||||
export interface ExecuteScriptParams {
|
||||
name: string;
|
||||
lang: string;
|
||||
script: string;
|
||||
indexPatternTitle: string;
|
||||
query?: Query['query'];
|
||||
|
@ -38,7 +37,7 @@ export interface ExecuteScriptParams {
|
|||
|
||||
export interface ExecuteScriptResult {
|
||||
status: number;
|
||||
hits: { hits: any[] };
|
||||
hits?: { hits: any[] };
|
||||
error?: any;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
*/
|
||||
|
||||
import { PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { registerPreviewScriptedFieldRoute, registerResolveIndexRoute } from './routes';
|
||||
|
||||
export class IndexPatternManagementPlugin implements Plugin<void, void> {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
@ -26,42 +27,8 @@ export class IndexPatternManagementPlugin implements Plugin<void, void> {
|
|||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/index-pattern-management/resolve_index/{query}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
query: schema.string(),
|
||||
}),
|
||||
query: schema.object({
|
||||
expand_wildcards: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal('all'),
|
||||
schema.literal('open'),
|
||||
schema.literal('closed'),
|
||||
schema.literal('hidden'),
|
||||
schema.literal('none'),
|
||||
])
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const queryString = req.query.expand_wildcards
|
||||
? { expand_wildcards: req.query.expand_wildcards }
|
||||
: null;
|
||||
const result = await context.core.elasticsearch.legacy.client.callAsCurrentUser(
|
||||
'transport.request',
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/_resolve/index/${encodeURIComponent(req.params.query)}${
|
||||
queryString ? '?' + new URLSearchParams(queryString).toString() : ''
|
||||
}`,
|
||||
}
|
||||
);
|
||||
return res.ok({ body: result });
|
||||
}
|
||||
);
|
||||
registerPreviewScriptedFieldRoute(router);
|
||||
registerResolveIndexRoute(router);
|
||||
}
|
||||
|
||||
public start() {}
|
||||
|
|
21
src/plugins/index_pattern_management/server/routes/index.ts
Normal file
21
src/plugins/index_pattern_management/server/routes/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * from './preview_scripted_field';
|
||||
export * from './resolve_index';
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 { CoreSetup, RequestHandlerContext } from 'src/core/server';
|
||||
import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks';
|
||||
import { registerPreviewScriptedFieldRoute } from './preview_scripted_field';
|
||||
|
||||
describe('preview_scripted_field route', () => {
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCoreSetup = coreMock.createSetup();
|
||||
});
|
||||
|
||||
it('handler calls /_search with the given request', async () => {
|
||||
const response = { body: { responses: [{ hits: { _id: 'hi' } }] } };
|
||||
const mockClient = { search: jest.fn().mockResolvedValue(response) };
|
||||
const mockContext = {
|
||||
core: {
|
||||
elasticsearch: { client: { asCurrentUser: mockClient } },
|
||||
},
|
||||
};
|
||||
const mockBody = {
|
||||
index: 'kibana_sample_data_logs',
|
||||
name: 'my_scripted_field',
|
||||
script: `doc['foo'].value`,
|
||||
};
|
||||
const mockQuery = {};
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
body: mockBody,
|
||||
query: mockQuery,
|
||||
});
|
||||
const mockResponse = httpServerMock.createResponseFactory();
|
||||
|
||||
registerPreviewScriptedFieldRoute(mockCoreSetup.http.createRouter());
|
||||
|
||||
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
|
||||
const handler = mockRouter.post.mock.calls[0][1];
|
||||
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
|
||||
|
||||
expect(mockClient.search.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_source": undefined,
|
||||
"body": Object {
|
||||
"query": Object {
|
||||
"match_all": Object {},
|
||||
},
|
||||
"script_fields": Object {
|
||||
"my_scripted_field": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "doc['foo'].value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"index": "kibana_sample_data_logs",
|
||||
"size": 10,
|
||||
"timeout": "30s",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: response });
|
||||
});
|
||||
|
||||
it('uses optional parameters when they are provided', async () => {
|
||||
const response = { body: { responses: [{ hits: { _id: 'hi' } }] } };
|
||||
const mockClient = { search: jest.fn().mockResolvedValue(response) };
|
||||
const mockContext = {
|
||||
core: {
|
||||
elasticsearch: { client: { asCurrentUser: mockClient } },
|
||||
},
|
||||
};
|
||||
const mockBody = {
|
||||
index: 'kibana_sample_data_logs',
|
||||
name: 'my_scripted_field',
|
||||
script: `doc['foo'].value`,
|
||||
query: {
|
||||
bool: { some: 'query' },
|
||||
},
|
||||
additionalFields: ['a', 'b', 'c'],
|
||||
};
|
||||
const mockQuery = {};
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
body: mockBody,
|
||||
query: mockQuery,
|
||||
});
|
||||
const mockResponse = httpServerMock.createResponseFactory();
|
||||
|
||||
registerPreviewScriptedFieldRoute(mockCoreSetup.http.createRouter());
|
||||
|
||||
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
|
||||
const handler = mockRouter.post.mock.calls[0][1];
|
||||
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
|
||||
|
||||
expect(mockClient.search.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_source": Array [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
],
|
||||
"body": Object {
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"some": "query",
|
||||
},
|
||||
},
|
||||
"script_fields": Object {
|
||||
"my_scripted_field": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "doc['foo'].value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"index": "kibana_sample_data_logs",
|
||||
"size": 10,
|
||||
"timeout": "30s",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handler throws an error if the search throws an error', async () => {
|
||||
const response = {
|
||||
statusCode: 400,
|
||||
message: 'oops',
|
||||
};
|
||||
const mockClient = { search: jest.fn().mockReturnValue(Promise.reject(response)) };
|
||||
const mockContext = {
|
||||
core: {
|
||||
elasticsearch: { client: { asCurrentUser: mockClient } },
|
||||
},
|
||||
};
|
||||
const mockBody = { searches: [{ header: {}, body: {} }] };
|
||||
const mockQuery = {};
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
body: mockBody,
|
||||
query: mockQuery,
|
||||
});
|
||||
const mockResponse = httpServerMock.createResponseFactory();
|
||||
|
||||
registerPreviewScriptedFieldRoute(mockCoreSetup.http.createRouter());
|
||||
|
||||
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
|
||||
const handler = mockRouter.post.mock.calls[0][1];
|
||||
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
|
||||
|
||||
expect(mockClient.search).toBeCalled();
|
||||
expect(mockResponse.customError).toBeCalled();
|
||||
|
||||
const error: any = mockResponse.customError.mock.calls[0][0];
|
||||
expect(error.statusCode).toBe(400);
|
||||
expect(error.body).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"error": "oops",
|
||||
},
|
||||
"message": "oops",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from 'src/core/server';
|
||||
|
||||
export function registerPreviewScriptedFieldRoute(router: IRouter): void {
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/index-pattern-management/preview_scripted_field',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
index: schema.string(),
|
||||
name: schema.string(),
|
||||
script: schema.string(),
|
||||
query: schema.maybe(schema.object({}, { unknowns: 'allow' })),
|
||||
additionalFields: schema.maybe(schema.arrayOf(schema.string())),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, res) => {
|
||||
const client = context.core.elasticsearch.client.asCurrentUser;
|
||||
const { index, name, script, query, additionalFields } = request.body;
|
||||
|
||||
try {
|
||||
const response = await client.search({
|
||||
index,
|
||||
_source: additionalFields && additionalFields.length > 0 ? additionalFields : undefined,
|
||||
size: 10,
|
||||
timeout: '30s',
|
||||
body: {
|
||||
query: query ?? { match_all: {} },
|
||||
script_fields: {
|
||||
[name]: {
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: script,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (err) {
|
||||
return res.customError({
|
||||
statusCode: err.statusCode || 500,
|
||||
body: {
|
||||
message: err.message,
|
||||
attributes: {
|
||||
error: err.body?.error || err.message,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from 'src/core/server';
|
||||
|
||||
export function registerResolveIndexRoute(router: IRouter): void {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/index-pattern-management/resolve_index/{query}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
query: schema.string(),
|
||||
}),
|
||||
query: schema.object({
|
||||
expand_wildcards: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal('all'),
|
||||
schema.literal('open'),
|
||||
schema.literal('closed'),
|
||||
schema.literal('hidden'),
|
||||
schema.literal('none'),
|
||||
])
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const queryString = req.query.expand_wildcards
|
||||
? { expand_wildcards: req.query.expand_wildcards }
|
||||
: null;
|
||||
const result = await context.core.elasticsearch.legacy.client.callAsCurrentUser(
|
||||
'transport.request',
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/_resolve/index/${encodeURIComponent(req.params.query)}${
|
||||
queryString ? '?' + new URLSearchParams(queryString).toString() : ''
|
||||
}`,
|
||||
}
|
||||
);
|
||||
return res.ok({ body: result });
|
||||
}
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue