[index patterns] Add pattern validation method to index patterns fetcher (#90170) (#90512)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Steph Milovic 2021-02-08 10:09:18 -07:00 committed by GitHub
parent ca0b1fe08c
commit 7d220d54c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 197 additions and 51 deletions

View file

@ -22,4 +22,5 @@ export declare class IndexPatternsFetcher
| --- | --- | --- |
| [getFieldsForTimePattern(options)](./kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsfortimepattern.md) | | Get a list of field objects for a time pattern |
| [getFieldsForWildcard(options)](./kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md) | | Get a list of field objects for an index pattern that may contain wildcards |
| [validatePatternListActive(patternList)](./kibana-plugin-plugins-data-server.indexpatternsfetcher.validatepatternlistactive.md) | | Returns an index pattern list of only those index pattern strings in the given list that return indices |

View file

@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [IndexPatternsFetcher](./kibana-plugin-plugins-data-server.indexpatternsfetcher.md) &gt; [validatePatternListActive](./kibana-plugin-plugins-data-server.indexpatternsfetcher.validatepatternlistactive.md)
## IndexPatternsFetcher.validatePatternListActive() method
Returns an index pattern list of only those index pattern strings in the given list that return indices
<b>Signature:</b>
```typescript
validatePatternListActive(patternList: string[]): Promise<string[]>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| patternList | <code>string[]</code> | |
<b>Returns:</b>
`Promise<string[]>`

View file

@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { IndexPatternsFetcher } from '.';
import { ElasticsearchClient } from 'kibana/server';
import * as indexNotFoundException from '../../../common/search/test_data/index_not_found_exception.json';
describe('Index Pattern Fetcher - server', () => {
let indexPatterns: IndexPatternsFetcher;
let esClient: ElasticsearchClient;
const emptyResponse = {
body: {
count: 0,
},
};
const response = {
body: {
count: 1115,
},
};
const patternList = ['a', 'b', 'c'];
beforeEach(() => {
esClient = ({
count: jest.fn().mockResolvedValueOnce(emptyResponse).mockResolvedValue(response),
} as unknown) as ElasticsearchClient;
indexPatterns = new IndexPatternsFetcher(esClient);
});
it('Removes pattern without matching indices', async () => {
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual(['b', 'c']);
});
it('Returns all patterns when all match indices', async () => {
esClient = ({
count: jest.fn().mockResolvedValue(response),
} as unknown) as ElasticsearchClient;
indexPatterns = new IndexPatternsFetcher(esClient);
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual(patternList);
});
it('Removes pattern when "index_not_found_exception" error is thrown', async () => {
class ServerError extends Error {
public body?: Record<string, any>;
constructor(
message: string,
public readonly statusCode: number,
errBody?: Record<string, any>
) {
super(message);
this.body = errBody;
}
}
esClient = ({
count: jest
.fn()
.mockResolvedValueOnce(response)
.mockRejectedValue(
new ServerError('index_not_found_exception', 404, indexNotFoundException)
),
} as unknown) as ElasticsearchClient;
indexPatterns = new IndexPatternsFetcher(esClient);
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual([patternList[0]]);
});
});

View file

@ -58,9 +58,16 @@ export class IndexPatternsFetcher {
rollupIndex?: string;
}): Promise<FieldDescriptor[]> {
const { pattern, metaFields, fieldCapsOptions, type, rollupIndex } = options;
const patternList = Array.isArray(pattern) ? pattern : pattern.split(',');
let patternListActive: string[] = patternList;
// if only one pattern, don't bother with validation. We let getFieldCapabilities fail if the single pattern is bad regardless
if (patternList.length > 1) {
patternListActive = await this.validatePatternListActive(patternList);
}
const fieldCapsResponse = await getFieldCapabilities(
this.elasticsearchClient,
pattern,
// if none of the patterns are active, pass the original list to get an error
patternListActive.length > 0 ? patternListActive : patternList,
metaFields,
{
allow_no_indices: fieldCapsOptions
@ -68,6 +75,7 @@ export class IndexPatternsFetcher {
: this.allowNoIndices,
}
);
if (type === 'rollup' && rollupIndex) {
const rollupFields: FieldDescriptor[] = [];
const rollupIndexCapabilities = getCapabilitiesForRollupIndices(
@ -118,4 +126,34 @@ export class IndexPatternsFetcher {
}
return await getFieldCapabilities(this.elasticsearchClient, indices, metaFields);
}
/**
* Returns an index pattern list of only those index pattern strings in the given list that return indices
*
* @param patternList string[]
* @return {Promise<string[]>}
*/
async validatePatternListActive(patternList: string[]) {
const result = await Promise.all(
patternList
.map((pattern) =>
this.elasticsearchClient.count({
index: pattern,
})
)
.map((p) =>
p.catch((e) => {
if (e.body.error.type === 'index_not_found_exception') {
return { body: { count: 0 } };
}
throw e;
})
)
);
return result.reduce(
(acc: string[], { body: { count } }, patternListIndex) =>
count > 0 ? [...acc, patternList[patternListIndex]] : acc,
[]
);
}
}

View file

@ -889,6 +889,7 @@ export class IndexPatternsFetcher {
type?: string;
rollupIndex?: string;
}): Promise<FieldDescriptor[]>;
validatePatternListActive(patternList: string[]): Promise<string[]>;
}
// Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts

View file

@ -17,6 +17,55 @@ export default function ({ getService }) {
expect(resp.body.fields).to.eql(sortBy(resp.body.fields, 'name'));
};
const testFields = [
{
type: 'boolean',
esTypes: ['boolean'],
searchable: true,
aggregatable: true,
name: 'bar',
readFromDocValues: true,
},
{
type: 'string',
esTypes: ['text'],
searchable: true,
aggregatable: false,
name: 'baz',
readFromDocValues: false,
},
{
type: 'string',
esTypes: ['keyword'],
searchable: true,
aggregatable: true,
name: 'baz.keyword',
readFromDocValues: true,
subType: { multi: { parent: 'baz' } },
},
{
type: 'number',
esTypes: ['long'],
searchable: true,
aggregatable: true,
name: 'foo',
readFromDocValues: true,
},
{
aggregatable: true,
esTypes: ['keyword'],
name: 'nestedField.child',
readFromDocValues: true,
searchable: true,
subType: {
nested: {
path: 'nestedField',
},
},
type: 'string',
},
];
describe('fields_for_wildcard_route response', () => {
before(() => esArchiver.load('index_patterns/basic_index'));
after(() => esArchiver.unload('index_patterns/basic_index'));
@ -26,54 +75,7 @@ export default function ({ getService }) {
.get('/api/index_patterns/_fields_for_wildcard')
.query({ pattern: 'basic_index' })
.expect(200, {
fields: [
{
type: 'boolean',
esTypes: ['boolean'],
searchable: true,
aggregatable: true,
name: 'bar',
readFromDocValues: true,
},
{
type: 'string',
esTypes: ['text'],
searchable: true,
aggregatable: false,
name: 'baz',
readFromDocValues: false,
},
{
type: 'string',
esTypes: ['keyword'],
searchable: true,
aggregatable: true,
name: 'baz.keyword',
readFromDocValues: true,
subType: { multi: { parent: 'baz' } },
},
{
type: 'number',
esTypes: ['long'],
searchable: true,
aggregatable: true,
name: 'foo',
readFromDocValues: true,
},
{
aggregatable: true,
esTypes: ['keyword'],
name: 'nestedField.child',
readFromDocValues: true,
searchable: true,
subType: {
nested: {
path: 'nestedField',
},
},
type: 'string',
},
],
fields: testFields,
})
.then(ensureFieldsAreSorted);
});
@ -162,11 +164,19 @@ export default function ({ getService }) {
.then(ensureFieldsAreSorted);
});
it('returns 404 when the pattern does not exist', async () => {
it('returns fields when one pattern exists and the other does not', async () => {
await supertest
.get('/api/index_patterns/_fields_for_wildcard')
.query({ pattern: 'bad_index,basic_index' })
.expect(200, {
fields: testFields,
});
});
it('returns 404 when no patterns exist', async () => {
await supertest
.get('/api/index_patterns/_fields_for_wildcard')
.query({
pattern: '[non-existing-pattern]its-invalid-*',
pattern: 'bad_index',
})
.expect(404);
});