This commit is contained in:
Chris Roberson 2018-03-09 10:15:36 -05:00 committed by GitHub
parent 7f5de0f99a
commit 076a0b2745
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 113 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

@ -36,7 +36,7 @@ exports[`StepIndexPattern should render normally 1`] = `
errors={Array []}
goToNextStep={[Function]}
isInputInvalid={false}
isNextStepDisabled={true}
isNextStepDisabled={false}
onQueryChanged={[Function]}
query="k"
/>
@ -54,14 +54,19 @@ exports[`StepIndexPattern should render normally 1`] = `
"name": "es",
},
],
"exactMatchedIndices": Array [],
"partialMatchedIndices": Array [],
"visibleIndices": Array [
"exactMatchedIndices": Array [
Object {
"name": "kibana",
},
],
"partialMatchedIndices": Array [
Object {
"name": "es",
"name": "kibana",
},
],
"visibleIndices": Array [
Object {
"name": "kibana",
},
],
}
@ -77,9 +82,6 @@ exports[`StepIndexPattern should render normally 1`] = `
Object {
"name": "kibana",
},
Object {
"name": "es",
},
]
}
/>
@ -120,11 +122,7 @@ exports[`StepIndexPattern should render some indices 1`] = `
"name": "kibana",
},
],
"partialMatchedIndices": Array [
Object {
"name": "kibana",
},
],
"partialMatchedIndices": Array [],
"visibleIndices": Array [
Object {
"name": "kibana",
@ -174,7 +172,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}
@ -182,16 +180,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"
@ -224,7 +218,7 @@ exports[`StepIndexPattern should show errors 1`] = `
],
}
}
query="?"
query="k"
/>
<EuiSpacer
size="s"
@ -240,3 +234,32 @@ exports[`StepIndexPattern should show errors 1`] = `
/>
</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,14 +15,13 @@ jest.mock('../../../lib/get_indices', () => ({
];
},
}));
jest.mock('../../../lib/is_query_a_match', () => ({ isQueryAMatch: () => true }));
const allIndices = [{ name: 'kibana' }, { name: 'es' }];
const esService = {};
const goToNextStep = () => {};
describe('StepIndexPattern', () => {
it('should render normally', () => {
it('should render normally', async () => {
const component = shallow(
<StepIndexPattern
allIndices={allIndices}
@ -33,6 +32,11 @@ describe('StepIndexPattern', () => {
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component).toMatchSnapshot();
});
@ -64,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();
});
@ -86,7 +89,6 @@ describe('StepIndexPattern', () => {
const instance = component.instance();
await instance.onQueryChanged({
nativeEvent: { data: '?' },
target: { value: '?' }
});
@ -101,11 +103,6 @@ describe('StepIndexPattern', () => {
allIndices={allIndices}
isIncludingSystemIndices={false}
esService={esService}
savedObjectsClient={{
find: () => ({ savedObjects: [
{ attributes: { title: 'k*' } }
] })
}}
goToNextStep={goToNextStep}
/>
);
@ -117,4 +114,23 @@ 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}
goToNextStep={goToNextStep}
initialQuery="k"
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component).toMatchSnapshot();
});
});

View file

@ -35,6 +35,7 @@ export class StepIndexPattern extends Component {
super(props);
this.state = {
partialMatchedIndices: [],
exactMatchedIndices: [],
isLoadingIndices: false,
query: props.initialQuery,
appendedWildcard: false,
@ -42,16 +43,32 @@ export class StepIndexPattern extends Component {
};
}
async componentWillMount() {
if (this.state.query) {
this.fetchIndices(this.state.query);
}
}
fetchIndices = async (query) => {
const { esService } = this.props;
this.setState({ isLoadingIndices: true });
const esQuery = query.endsWith('*') ? query : `${query}*`;
const partialMatchedIndices = await getIndices(esService, esQuery, MAX_SEARCH_SIZE);
createReasonableWait(() => this.setState({ partialMatchedIndices, isLoadingIndices: false }));
this.setState({ isLoadingIndices: true, indexPatternExists: 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;
@ -148,9 +165,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,84 +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' },
]);
});
expect(visibleIndices).toEqual(indices);
});
});

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 filterSystemIndices(indices, isIncludingSystemIndices) {
if (!indices) {
@ -14,18 +13,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;