[data views] Remove partial index pattern validation (#153350)

## Summary

https://github.com/elastic/kibana/pull/151788 established that data
views would no longer error when they failed to retrieve a field list.
This index pattern validation code exists since previously ALL index
pattern segments needed to match in order to avoid an error response
from field caps, rather than just one. Now we can remove the validation
code and simply pass the index pattern to field caps directly.


### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: shahzad31 <shahzad31comp@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Matthew Kime 2023-03-27 05:34:47 -05:00 committed by GitHub
parent cd96ab465b
commit 29f038071e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 21 additions and 98 deletions

View file

@ -334,6 +334,7 @@ export class DataViewEditorService {
if (this.type === INDEX_PATTERN_TYPE.ROLLUP) {
getFieldsOptions.type = INDEX_PATTERN_TYPE.ROLLUP;
getFieldsOptions.rollupIndex = currentState.rollupIndexName || '';
getFieldsOptions.allowNoIndex = true;
}
let timestampFieldOptions: TimestampOption[] = [];

View file

@ -9,14 +9,10 @@
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IndexPatternsFetcher } from '.';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import * as indexNotFoundException from './index_not_found_exception.json';
describe('Index Pattern Fetcher - server', () => {
let indexPatterns: IndexPatternsFetcher;
let esClient: ReturnType<typeof elasticsearchServiceMock.createElasticsearchClient>;
const emptyResponse = {
indices: [],
};
const response = {
indices: ['b'],
fields: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }],
@ -27,61 +23,7 @@ describe('Index Pattern Fetcher - server', () => {
esClient = elasticsearchServiceMock.createElasticsearchClient();
indexPatterns = new IndexPatternsFetcher(esClient);
});
it('Removes pattern without matching indices', async () => {
esClient.fieldCaps
.mockResponseOnce(emptyResponse as unknown as estypes.FieldCapsResponse)
.mockResponse(response as unknown as estypes.FieldCapsResponse);
// first field caps request returns empty
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual(['b', 'c']);
});
it('Keeps matching and negating patterns', async () => {
esClient.fieldCaps
.mockResponseOnce(emptyResponse as unknown as estypes.FieldCapsResponse)
.mockResponse(response as unknown as estypes.FieldCapsResponse);
// first field caps request returns empty
const result = await indexPatterns.validatePatternListActive(['-a', 'b', 'c', 'a:-b']);
expect(result).toEqual(['-a', 'c', 'a:-b']);
});
it('Returns all patterns when all match indices', async () => {
esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse);
indexPatterns = new IndexPatternsFetcher(esClient);
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual(patternList);
});
it('Removes pattern when 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.fieldCaps
.mockResponseOnce(response as unknown as estypes.FieldCapsResponse)
.mockImplementationOnce(() => {
return Promise.reject(
new ServerError('index_not_found_exception', 404, indexNotFoundException)
);
});
indexPatterns = new IndexPatternsFetcher(esClient);
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual([patternList[0]]);
});
it('When allowNoIndices is false, run validatePatternListActive', async () => {
esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse);
indexPatterns = new IndexPatternsFetcher(esClient);
await indexPatterns.getFieldsForWildcard({ pattern: patternList });
expect(esClient.fieldCaps).toHaveBeenCalledTimes(4);
});
it('When allowNoIndices is true, do not run validatePatternListActive', async () => {
it('calls fieldcaps once', async () => {
esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse);
indexPatterns = new IndexPatternsFetcher(esClient, true);
await indexPatterns.getFieldsForWildcard({ pattern: patternList });

View file

@ -64,18 +64,13 @@ export class IndexPatternsFetcher {
fields?: string[];
}): Promise<{ fields: FieldDescriptor[]; indices: string[] }> {
const { pattern, metaFields = [], fieldCapsOptions, type, rollupIndex, indexFilter } = options;
const patternList = Array.isArray(pattern) ? pattern : pattern.split(',');
const allowNoIndices = fieldCapsOptions
? fieldCapsOptions.allow_no_indices
: this.allowNoIndices;
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 && !allowNoIndices) {
patternListActive = await this.validatePatternListActive(patternList);
}
const fieldCapsResponse = await getFieldCapabilities({
callCluster: this.elasticsearchClient,
indices: patternListActive,
indices: pattern,
metaFields,
fieldCapsOptions: {
allow_no_indices: allowNoIndices,
@ -84,6 +79,7 @@ export class IndexPatternsFetcher {
indexFilter,
fields: options.fields || ['*'],
});
if (type === 'rollup' && rollupIndex) {
const rollupFields: FieldDescriptor[] = [];
const capabilityCheck = getCapabilitiesForRollupIndices(
@ -114,35 +110,4 @@ export class IndexPatternsFetcher {
}
return fieldCapsResponse;
}
/**
* 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(async (index) => {
// perserve negated patterns
if (index.startsWith('-') || index.includes(':-')) {
return true;
}
const searchResponse = await this.elasticsearchClient.fieldCaps({
index,
fields: '_id',
ignore_unavailable: true,
allow_no_indices: false,
});
return searchResponse.indices.length > 0;
})
.map((p) => p.catch(() => false))
);
return result.reduce(
(acc: string[], isValid, patternListIndex) =>
isValid ? [...acc, patternList[patternListIndex]] : acc,
[]
);
}
}

View file

@ -201,6 +201,14 @@ export default function ({ getService }) {
indices: ['basic_index'],
});
});
it('returns 404 when neither exists', async () => {
await supertest
.get('/api/index_patterns/_fields_for_wildcard')
.query({ pattern: 'bad_index,bad_index_2' })
.expect(404);
});
it('returns 404 when no patterns exist', async () => {
await supertest
.get('/api/index_patterns/_fields_for_wildcard')

View file

@ -106,6 +106,7 @@ describe('ObservabilityDataViews', function () {
timeFieldName: '@timestamp',
title: 'trace-*,apm-*',
name: 'User experience (RUM)',
allowNoIndex: true,
});
expect(dataViews?.createAndSave).toHaveBeenCalledTimes(1);

View file

@ -132,11 +132,16 @@ export class ObservabilityDataViews {
timeFieldName: '@timestamp',
fieldFormats: this.getFieldFormats(app),
name: DataTypesLabels[app],
allowNoIndex: true,
},
false,
false
);
if (dataView.matchedIndices.length === 0) {
return;
}
if (runtimeFields !== null) {
runtimeFields.forEach(({ name, field }) => {
dataView.addRuntimeField(name, field);
@ -162,6 +167,7 @@ export class ObservabilityDataViews {
timeFieldName: '@timestamp',
fieldFormats: this.getFieldFormats(app),
name: DataTypesLabels[app],
allowNoIndex: true,
});
}
// we want to make sure field formats remain same

View file

@ -461,7 +461,7 @@ export default async function ({ readConfigFile }) {
elasticsearch: {
indices: [
{
names: ['rollup-*'],
names: ['rollup-*', 'regular-index*'],
privileges: ['read', 'view_index_metadata'],
},
],