Skip to content

Commit

Permalink
[index patterns] Add pattern validation method to index patterns fetc…
Browse files Browse the repository at this point in the history
…her (elastic#90170)
  • Loading branch information
stephmilovic authored Feb 5, 2021
1 parent a7b46a9 commit fc516ba
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Original file line number Diff line number Diff line change
@@ -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[]>`

Original file line number Diff line number Diff line change
@@ -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]]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,24 @@ 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
? fieldCapsOptions.allow_no_indices
: this.allowNoIndices,
}
);

if (type === 'rollup' && rollupIndex) {
const rollupFields: FieldDescriptor[] = [];
const rollupIndexCapabilities = getCapabilitiesForRollupIndices(
Expand Down Expand Up @@ -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,
[]
);
}
}
1 change: 1 addition & 0 deletions src/plugins/data/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down

0 comments on commit fc516ba

Please sign in to comment.