mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
This commit is contained in:
parent
b3b0a5d7ce
commit
5c7c39aaf3
10 changed files with 207 additions and 224 deletions
|
@ -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.
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,3 +1,3 @@
|
|||
export function createReasonableWait(cb) {
|
||||
return setTimeout(cb, 500);
|
||||
return setTimeout(cb, 100);
|
||||
}
|
||||
|
|
|
@ -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.';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue