Refactor to go back to the double query approach to support aliases (#16715) (#16786)

This commit is contained in:
Chris Roberson 2018-02-16 13:44:00 -05:00 committed by GitHub
parent b3b0a5d7ce
commit 5c7c39aaf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 207 additions and 224 deletions

View file

@ -0,0 +1,13 @@
# Create Index Pattern
This is meant to serve as a guide to this area of code.
## Bye bye regressions
In order to prevent future regressions, there are a few scenarios
that need to be tested with each change to this area of the code.
- Cross cluster search
- Ensure changes work properly in a CCS environment
- A solid CCS environment involves various indices on all nodes including the controlling node.
- Alias support
- Indices are the most common use case, but we also support aliases.

View file

@ -34,11 +34,7 @@ exports[`StepIndexPattern should disable the next step if the index pattern exis
"name": "kibana",
},
],
"partialMatchedIndices": Array [
Object {
"name": "kibana",
},
],
"partialMatchedIndices": Array [],
"visibleIndices": Array [
Object {
"name": "kibana",
@ -123,11 +119,7 @@ exports[`StepIndexPattern should properly fetch indices for the initial query 1`
"name": "kibana",
},
],
"partialMatchedIndices": Array [
Object {
"name": "kibana",
},
],
"partialMatchedIndices": Array [],
"visibleIndices": Array [
Object {
"name": "kibana",
@ -249,11 +241,7 @@ exports[`StepIndexPattern should render some indices 1`] = `
"name": "kibana",
},
],
"partialMatchedIndices": Array [
Object {
"name": "kibana",
},
],
"partialMatchedIndices": Array [],
"visibleIndices": Array [
Object {
"name": "kibana",
@ -304,7 +292,7 @@ exports[`StepIndexPattern should render the loading state 1`] = `
</EuiPanel>
`;
exports[`StepIndexPattern should show errors 1`] = `
exports[`StepIndexPattern should search for partial indices for queries not ending in a wildcard 1`] = `
<EuiPanel
grow={true}
hasShadow={false}
@ -312,16 +300,12 @@ exports[`StepIndexPattern should show errors 1`] = `
>
<Header
characterList="\\\\, /, ?, \\", <, >, |"
errors={
Array [
"Your input contains invalid characters or spaces. Please omit: \\\\, /, ?, \\", <, >, |",
]
}
errors={Array []}
goToNextStep={[Function]}
isInputInvalid={true}
isNextStepDisabled={true}
isInputInvalid={false}
isNextStepDisabled={false}
onQueryChanged={[Function]}
query="?"
query="k"
/>
<EuiSpacer
size="s"
@ -354,7 +338,7 @@ exports[`StepIndexPattern should show errors 1`] = `
],
}
}
query="?"
query="k"
/>
<EuiSpacer
size="s"
@ -367,7 +351,36 @@ exports[`StepIndexPattern should show errors 1`] = `
},
]
}
query="?"
query="k"
/>
</EuiPanel>
`;
exports[`StepIndexPattern should show errors 1`] = `
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="l"
>
<Header
characterList="\\\\, /, ?, \\", <, >, |"
errors={
Array [
"Your input contains invalid characters or spaces. Please omit: \\\\, /, ?, \\", <, >, |",
]
}
goToNextStep={[Function]}
isInputInvalid={true}
isNextStepDisabled={true}
onQueryChanged={[Function]}
query="?"
/>
<EuiSpacer
size="s"
/>
<LoadingIndices />
<EuiSpacer
size="s"
/>
</EuiPanel>
`;

View file

@ -15,7 +15,6 @@ jest.mock('../../../lib/get_indices', () => ({
];
},
}));
jest.mock('../../../lib/is_query_a_match', () => ({ isQueryAMatch: () => true }));
const allIndices = [{ name: 'kibana' }, { name: 'es' }];
const esService = {};
@ -69,11 +68,10 @@ describe('StepIndexPattern', () => {
const instance = component.instance();
await instance.onQueryChanged({
nativeEvent: { data: 'k' },
target: { value: 'k' }
});
component.update();
await component.update();
expect(component).toMatchSnapshot();
});
@ -92,7 +90,6 @@ describe('StepIndexPattern', () => {
const instance = component.instance();
await instance.onQueryChanged({
nativeEvent: { data: '?' },
target: { value: '?' }
});
@ -167,4 +164,25 @@ describe('StepIndexPattern', () => {
expect(component).toMatchSnapshot();
});
it('should search for partial indices for queries not ending in a wildcard', async () => {
const component = shallow(
<StepIndexPattern
allIndices={allIndices}
isIncludingSystemIndices={false}
esService={esService}
savedObjectsClient={savedObjectsClient}
goToNextStep={goToNextStep}
initialQuery="k"
/>
);
// Allow the componentWillMount code to execute
// https://github.com/airbnb/enzyme/issues/450
await component.update(); // Fire `componentWillMount()`
await component.update(); // Force update the component post async actions
await component.update(); // There are two actions so we apparently need to call this again
expect(component).toMatchSnapshot();
});
});

View file

@ -37,6 +37,7 @@ export class StepIndexPattern extends Component {
super(props);
this.state = {
partialMatchedIndices: [],
exactMatchedIndices: [],
isLoadingIndices: false,
existingIndexPatterns: [],
indexPatternExists: false,
@ -73,12 +74,22 @@ export class StepIndexPattern extends Component {
}
this.setState({ isLoadingIndices: true, indexPatternExists: false });
const esQuery = query.endsWith('*') ? query : `${query}*`;
const partialMatchedIndices = await getIndices(esService, esQuery, MAX_SEARCH_SIZE);
createReasonableWait(() => this.setState({ partialMatchedIndices, isLoadingIndices: false }));
if (query.endsWith('*')) {
const exactMatchedIndices = await getIndices(esService, query, MAX_SEARCH_SIZE);
createReasonableWait(() => this.setState({ exactMatchedIndices, isLoadingIndices: false }));
}
else {
const partialMatchedIndices = await getIndices(esService, `${query}*`, MAX_SEARCH_SIZE);
const exactMatchedIndices = await getIndices(esService, query, MAX_SEARCH_SIZE);
createReasonableWait(() => this.setState({
partialMatchedIndices,
exactMatchedIndices,
isLoadingIndices: false
}));
}
}
onQueryChanged = (e) => {
onQueryChanged = e => {
const { appendedWildcard } = this.state;
const { target } = e;
@ -199,9 +210,15 @@ export class StepIndexPattern extends Component {
render() {
const { isIncludingSystemIndices, allIndices } = this.props;
const { query, partialMatchedIndices } = this.state;
const { query, partialMatchedIndices, exactMatchedIndices } = this.state;
const matchedIndices = getMatchedIndices(allIndices, partialMatchedIndices, query, isIncludingSystemIndices);
const matchedIndices = getMatchedIndices(
allIndices,
partialMatchedIndices,
exactMatchedIndices,
query,
isIncludingSystemIndices
);
return (
<EuiPanel paddingSize="l">

View file

@ -13,121 +13,95 @@ const indices = [
{ name: '.kibana' }
];
const partialIndices = [
{ name: 'kibana' },
{ name: 'es' },
{ name: '.kibana' },
];
const exactIndices = [
{ name: 'kibana' },
{ name: '.kibana' },
];
describe('getMatchedIndices', () => {
describe('allIndices', () => {
it('should return all indices', () => {
const query = 'ki';
const { allIndices } = getMatchedIndices(indices, indices, query, true);
expect(allIndices).toEqual(indices);
});
it('should return all indices', () => {
const {
allIndices,
exactMatchedIndices,
partialMatchedIndices,
visibleIndices,
} = getMatchedIndices(indices, partialIndices, exactIndices, '*', true);
it('should return all indices except for system indices', () => {
const query = 'ki';
const { allIndices } = getMatchedIndices(indices, indices, query, false);
expect(allIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
{ name: 'logstash' },
{ name: 'packetbeat' },
{ name: 'metricbeat' },
]);
});
expect(allIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
{ name: 'logstash' },
{ name: 'packetbeat' },
{ name: 'metricbeat' },
{ name: '.kibana' },
]);
expect(exactMatchedIndices).toEqual([
{ name: 'kibana' },
{ name: '.kibana' },
]);
expect(partialMatchedIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
{ name: '.kibana' },
]);
expect(visibleIndices).toEqual([
{ name: 'kibana' },
{ name: '.kibana' },
]);
});
describe('exactMatchedIndices', () => {
it('should return all exact matched indices', () => {
const query = 'ki*';
const { exactMatchedIndices } = getMatchedIndices(indices, indices, query, true);
expect(exactMatchedIndices).toEqual([
{ name: 'kibana' },
{ name: '.kibana' },
]);
});
it('should return all indices except for system indices', () => {
const {
allIndices,
exactMatchedIndices,
partialMatchedIndices,
visibleIndices,
} = getMatchedIndices(indices, partialIndices, exactIndices, '*', false);
it('should return all exact matched indices except for system indices', () => {
const query = 'ki*';
const { exactMatchedIndices } = getMatchedIndices(indices, indices, query, false);
expect(exactMatchedIndices).toEqual([
{ name: 'kibana' },
]);
});
expect(allIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
{ name: 'logstash' },
{ name: 'packetbeat' },
{ name: 'metricbeat' },
]);
expect(exactMatchedIndices).toEqual([
{ name: 'kibana' },
]);
expect(partialMatchedIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
]);
expect(visibleIndices).toEqual([
{ name: 'kibana' },
]);
});
describe('partialMatchedIndices', () => {
it('should return all partial matched indices', () => {
const query = 'ki*';
const partialIndices = indices.slice(1);
const { partialMatchedIndices } = getMatchedIndices(indices, partialIndices, query, true);
expect(partialMatchedIndices).toEqual(partialIndices);
});
it('should return partial matches as visible if there are no exact', () => {
const { visibleIndices } = getMatchedIndices(indices, partialIndices, [], '*', true);
it('should return all partial matched indices except for system indices', () => {
const query = 'ki*';
const partialIndices = indices.slice(1);
const { partialMatchedIndices } = getMatchedIndices(indices, partialIndices, query, false);
expect(partialMatchedIndices).toEqual([
{ name: 'es' },
{ name: 'logstash' },
{ name: 'packetbeat' },
{ name: 'metricbeat' },
]);
});
expect(visibleIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
{ name: '.kibana' },
]);
});
describe('visibleIndices', () => {
it('should return all visible indices', () => {
const query = 'foo*';
const { visibleIndices } = getMatchedIndices(indices, indices, query, true);
expect(visibleIndices).toEqual(indices);
});
it('should return all indices as visible if there are no exact or partial', () => {
const { visibleIndices } = getMatchedIndices(indices, [], [], '*', true);
it('should return all visible indices except for system indices', () => {
const query = 'foo*';
const { visibleIndices } = getMatchedIndices(indices, indices, query, false);
expect(visibleIndices).toEqual([
{ name: 'kibana' },
{ name: 'es' },
{ name: 'logstash' },
{ name: 'packetbeat' },
{ name: 'metricbeat' },
]);
});
});
describe('systemIndices', () => {
it('should return all visible ccs indices', () => {
const query = 'cluster_one:*';
const indices = [
{ name: 'cluster_one:kibana' },
{ name: 'cluster_one:es' },
{ name: 'cluster_two:kibana' },
{ name: 'kibana' },
{ name: 'cluster_one:.kibana' },
];
const { visibleIndices } = getMatchedIndices(indices, indices, query, true);
expect(visibleIndices).toEqual([
{ name: 'cluster_one:kibana' },
{ name: 'cluster_one:es' },
{ name: 'cluster_one:.kibana' },
]);
});
it('should return all visible ccs indices except for system indices', () => {
const query = 'cluster_one:*';
const indices = [
{ name: 'cluster_one:kibana' },
{ name: 'cluster_one:es' },
{ name: 'cluster_two:kibana' },
{ name: 'kibana' },
{ name: 'cluster_one:.kibana' },
];
const { visibleIndices } = getMatchedIndices(indices, indices, query, false);
expect(visibleIndices).toEqual([
{ name: 'cluster_one:kibana' },
{ name: 'cluster_one:es' },
]);
});
expect(visibleIndices).toEqual(indices);
});
});

View file

@ -1,44 +0,0 @@
import { isQueryAMatch } from '../is_query_a_match';
describe('isQueryAMatch', () => {
describe('returns true', () => {
it('for an exact match', () => {
expect(isQueryAMatch('kibana', 'kibana')).toBeTruthy();
});
it('for a pattern with a trailing wildcard', () => {
expect(isQueryAMatch('ki*', 'kibana')).toBeTruthy();
});
it('for a pattern with a leading wildcard', () => {
expect(isQueryAMatch('*ki*', 'kibana')).toBeTruthy();
});
it('for a pattern with a middle and trailing wildcard', () => {
expect(isQueryAMatch('k*b*', 'kibana')).toBeTruthy();
});
it('for a pattern that is only a wildcard', () => {
expect(isQueryAMatch('*', 'es')).toBeTruthy();
});
it('for a pattern that contains commas', () => {
expect(isQueryAMatch('cluster_one:kibana,cluster_two:kibana', 'cluster_one:kibana')).toBeTruthy();
expect(isQueryAMatch('cluster_one:k*,cluster_two:kibana', 'cluster_one:kibana')).toBeTruthy();
});
});
describe('returns false', () => {
it('for a pattern with a middle wildcard only and is not an exact match', () => {
expect(isQueryAMatch('k*b', 'kibana')).toBeFalsy();
});
it('for a pattern with wildcards but does not remotely match', () => {
expect(isQueryAMatch('k*b*', 'es')).toBeFalsy();
});
it('for a pattern that contains commas but is not a CCS query', () => {
expect(isQueryAMatch('kibana,es', 'kibana')).toBeFalsy();
});
});
});

View file

@ -1,3 +1,3 @@
export function createReasonableWait(cb) {
return setTimeout(cb, 500);
return setTimeout(cb, 100);
}

View file

@ -11,6 +11,11 @@ export async function getIndices(es, rawPattern, limit) {
return [];
}
// This should never match anything so do not bother
if (pattern === '') {
return [];
}
// We need to always provide a limit and not rely on the default
if (!limit) {
throw '`getIndices()` was called without the required `limit` parameter.';

View file

@ -1,5 +1,4 @@
import { MAX_NUMBER_OF_MATCHING_INDICES } from '../constants';
import { isQueryAMatch } from './is_query_a_match';
function isSystemIndex(index) {
if (index.startsWith('.')) {
@ -26,18 +25,40 @@ function filterSystemIndices(indices, isIncludingSystemIndices) {
return acceptableIndices.slice(0, MAX_NUMBER_OF_MATCHING_INDICES);
}
/**
This utility is designed to do a couple of things:
1) Take in list of indices and filter out system indices if necessary
2) Return a `visible` list based on a priority order.
We are passing in three separate lists because they each represent
something slightly different.
- `unfilteredAllIndices`
This is the result of the initial `*` query and represents all known indices
- `unfilteredPartialMatchedIndices`
This is the result of searching against the query with an added `*`. This is only
used when the query does not end in an `*` and represents potential matches in the UI
- `unfilteredExactMatchedIndices
This is the result of searching against a query that already ends in `*`.
We call this `exact` matches because ES is telling us exactly what it matches
*/
export function getMatchedIndices(
unfilteredAllIndices,
unfilteredPartialMatchedIndices,
unfilteredExactMatchedIndices,
query,
isIncludingSystemIndices
) {
const allIndices = filterSystemIndices(unfilteredAllIndices, isIncludingSystemIndices);
const partialMatchedIndices = filterSystemIndices(unfilteredPartialMatchedIndices, isIncludingSystemIndices);
const exactMatchedIndices = filterSystemIndices(unfilteredExactMatchedIndices, isIncludingSystemIndices);
const exactIndices = partialMatchedIndices.filter(({ name }) => isQueryAMatch(query, name));
const exactMatchedIndices = filterSystemIndices(exactIndices, isIncludingSystemIndices);
// We need to pick one to show in the UI and there is a priority here
// 1) If there are exact matches, show those as the query is good to go
// 2) If there are no exact matches, but there are partial matches,
// show the partial matches
// 3) If there are no exact or partial matches, just show all indices
let visibleIndices;
if (exactMatchedIndices.length) {
visibleIndices = exactMatchedIndices;

View file

@ -1,34 +0,0 @@
function isCCSQuery(query) {
return query.includes(':');
}
export const isQueryAMatch = (query, name) => {
if (name === query) {
return true;
}
const regexQuery = query
.replace(/[*]/g, '.*')
.replace(/[+]/g, '\\+');
// This shouldn't be necessary but just used as a safety net
// so the page doesn't bust if the user types in some weird
// query that throws an exception when converting to a RegExp
try {
const regex = new RegExp(regexQuery);
if (regex.test(name) && (query.endsWith('*') || query.length === name.length)) {
return true;
}
}
catch (e) {
return false;
}
if (query.includes(',')) {
return query.split(',').reduce((isMatch, subQuery) => {
return isMatch || isCCSQuery(subQuery) && isQueryAMatch(subQuery, name);
}, false);
}
return false;
};