mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* Retrieve autocomplete fields when a request has completed. - Updating polling logic so that dispatching a request resets the poll timer.
This commit is contained in:
parent
70b0d7f825
commit
6de6e36622
6 changed files with 174 additions and 176 deletions
|
@ -34,7 +34,7 @@ describe('app initialization', () => {
|
|||
ajaxDoneStub = sinon.stub();
|
||||
sandbox.stub($, 'ajax').returns({ done: ajaxDoneStub });
|
||||
sandbox.stub(history, 'getSavedEditorState');
|
||||
sandbox.stub(mappings, 'startRetrievingAutoCompleteInfo');
|
||||
sandbox.stub(mappings, 'startPolling');
|
||||
|
||||
inputMock = {
|
||||
update: sinon.stub(),
|
||||
|
|
|
@ -131,5 +131,5 @@ export default function init(input, output, sourceLocation = 'stored') {
|
|||
};
|
||||
setupAutosave();
|
||||
loadSavedState();
|
||||
mappings.startRetrievingAutoCompleteInfo();
|
||||
mappings.startPolling();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
require('brace');
|
||||
require('brace/ext/searchbox');
|
||||
import Autocomplete from './autocomplete';
|
||||
import mappings from './mappings';
|
||||
const SenseEditor = require('./sense_editor/editor');
|
||||
const settings = require('./settings');
|
||||
const utils = require('./utils');
|
||||
|
@ -110,13 +111,9 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
if (reqId !== CURRENT_REQ_ID) {
|
||||
return;
|
||||
}
|
||||
let xhr;
|
||||
if (dataOrjqXHR.promise) {
|
||||
xhr = dataOrjqXHR;
|
||||
}
|
||||
else {
|
||||
xhr = jqXhrORerrorThrown;
|
||||
}
|
||||
|
||||
const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;
|
||||
|
||||
function modeForContentType(contentType) {
|
||||
if (contentType.indexOf('text/plain') >= 0) {
|
||||
return 'ace/mode/text';
|
||||
|
@ -127,14 +124,20 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
return null;
|
||||
}
|
||||
|
||||
if (typeof xhr.status === 'number' &&
|
||||
// things like DELETE index where the index is not there are OK.
|
||||
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404)
|
||||
) {
|
||||
const isSuccess = typeof xhr.status === 'number' &&
|
||||
// Things like DELETE index where the index is not there are OK.
|
||||
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
|
||||
|
||||
if (isSuccess) {
|
||||
if (xhr.status !== 404) {
|
||||
// If the user has submitted a request against ES, something in the fields, indices, aliases,
|
||||
// or templates may have changed, so we'll need to update this data.
|
||||
mappings.retrieveAutoCompleteInfo();
|
||||
}
|
||||
|
||||
// we have someone on the other side. Add to history
|
||||
history.addToHistory(esPath, esMethod, esData);
|
||||
|
||||
|
||||
let value = xhr.responseText;
|
||||
const mode = modeForContentType(xhr.getAllResponseHeaders('Content-Type') || '');
|
||||
|
||||
|
@ -166,8 +169,7 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
isFirstRequest = false;
|
||||
// single request terminate via sendNextRequest as well
|
||||
sendNextRequest();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let value;
|
||||
let mode;
|
||||
if (xhr.responseText) {
|
||||
|
|
|
@ -22,7 +22,10 @@ const _ = require('lodash');
|
|||
const es = require('./es');
|
||||
const settings = require('./settings');
|
||||
|
||||
|
||||
// NOTE: If this value ever changes to be a few seconds or less, it might introduce flakiness
|
||||
// due to timing issues in our app.js tests.
|
||||
const POLL_INTERVAL = 60000;
|
||||
let pollTimeoutId;
|
||||
|
||||
let perIndexTypes = {};
|
||||
let perAliasIndexes = [];
|
||||
|
@ -61,12 +64,13 @@ function expandAliases(indicesOrAliases) {
|
|||
function getTemplates() {
|
||||
return [ ...templates ];
|
||||
}
|
||||
|
||||
function getFields(indices, types) {
|
||||
// get fields for indices and types. Both can be a list, a string or null (meaning all).
|
||||
let ret = [];
|
||||
indices = expandAliases(indices);
|
||||
if (typeof indices === 'string') {
|
||||
|
||||
if (typeof indices === 'string') {
|
||||
const typeDict = perIndexTypes[indices];
|
||||
if (!typeDict) {
|
||||
return [];
|
||||
|
@ -75,8 +79,7 @@ function getFields(indices, types) {
|
|||
if (typeof types === 'string') {
|
||||
const f = typeDict[types];
|
||||
ret = f ? f : [];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// filter what we need
|
||||
$.each(typeDict, function (type, fields) {
|
||||
if (!types || types.length === 0 || $.inArray(type, types) !== -1) {
|
||||
|
@ -86,8 +89,7 @@ function getFields(indices, types) {
|
|||
|
||||
ret = [].concat.apply([], ret);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// multi index mode.
|
||||
$.each(perIndexTypes, function (index) {
|
||||
if (!indices || indices.length === 0 || $.inArray(index, indices) !== -1) {
|
||||
|
@ -128,10 +130,8 @@ function getTypes(indices) {
|
|||
}
|
||||
|
||||
return _.uniq(ret);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getIndices(includeAliases) {
|
||||
const ret = [];
|
||||
$.each(perIndexTypes, function (index) {
|
||||
|
@ -164,7 +164,7 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) {
|
|||
|
||||
if (fieldMapping.properties) {
|
||||
// derived object type
|
||||
nestedFields = getFieldNamesFromTypeMapping(fieldMapping);
|
||||
nestedFields = getFieldNamesFromProperties(fieldMapping.properties);
|
||||
return applyPathSettings(nestedFields);
|
||||
}
|
||||
|
||||
|
@ -196,9 +196,9 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) {
|
|||
return [ret];
|
||||
}
|
||||
|
||||
function getFieldNamesFromTypeMapping(typeMapping) {
|
||||
function getFieldNamesFromProperties(properties = {}) {
|
||||
const fieldList =
|
||||
$.map(typeMapping.properties || {}, function (fieldMapping, fieldName) {
|
||||
$.map(properties, function (fieldMapping, fieldName) {
|
||||
return getFieldNamesFromFieldMapping(fieldName, fieldMapping);
|
||||
});
|
||||
|
||||
|
@ -214,16 +214,24 @@ function loadTemplates(templatesObject = {}) {
|
|||
|
||||
function loadMappings(mappings) {
|
||||
perIndexTypes = {};
|
||||
|
||||
$.each(mappings, function (index, indexMapping) {
|
||||
const normalizedIndexMappings = {};
|
||||
// 1.0.0 mapping format has changed, extract underlying mapping
|
||||
|
||||
// Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping.
|
||||
if (indexMapping.mappings && _.keys(indexMapping).length === 1) {
|
||||
indexMapping = indexMapping.mappings;
|
||||
}
|
||||
|
||||
$.each(indexMapping, function (typeName, typeMapping) {
|
||||
const fieldList = getFieldNamesFromTypeMapping(typeMapping);
|
||||
normalizedIndexMappings[typeName] = fieldList;
|
||||
if (typeName === 'properties') {
|
||||
const fieldList = getFieldNamesFromProperties(typeMapping);
|
||||
normalizedIndexMappings[typeName] = fieldList;
|
||||
} else {
|
||||
normalizedIndexMappings[typeName] = [];
|
||||
}
|
||||
});
|
||||
|
||||
perIndexTypes[index] = normalizedIndexMappings;
|
||||
});
|
||||
}
|
||||
|
@ -256,20 +264,21 @@ function clear() {
|
|||
templates = [];
|
||||
}
|
||||
|
||||
function retrieveSettings(settingsKey, changedFields) {
|
||||
const autocompleteSettings = settings.getAutocomplete();
|
||||
function retrieveSettings(settingsKey, settingsToRetrieve) {
|
||||
const currentSettings = settings.getAutocomplete();
|
||||
const settingKeyToPathMap = {
|
||||
fields: '_mapping',
|
||||
indices: '_aliases',
|
||||
templates: '_template',
|
||||
};
|
||||
// Fetch autocomplete info if setting is set to true, and if user has made changes
|
||||
if (autocompleteSettings[settingsKey] && changedFields[settingsKey]) {
|
||||
|
||||
// Fetch autocomplete info if setting is set to true, and if user has made changes.
|
||||
if (currentSettings[settingsKey] && settingsToRetrieve[settingsKey]) {
|
||||
return es.send('GET', settingKeyToPathMap[settingsKey], null, null, true);
|
||||
} else {
|
||||
const settingsPromise = new $.Deferred();
|
||||
// If a user has saved settings, but a field remains checked and unchanged, no need to make changes
|
||||
if (autocompleteSettings[settingsKey]) {
|
||||
if (currentSettings[settingsKey]) {
|
||||
return settingsPromise.resolve();
|
||||
}
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist
|
||||
|
@ -277,10 +286,15 @@ function retrieveSettings(settingsKey, changedFields) {
|
|||
}
|
||||
}
|
||||
|
||||
function retrieveAutocompleteInfoFromServer(changedFields) {
|
||||
const mappingPromise = retrieveSettings('fields', changedFields);
|
||||
const aliasesPromise = retrieveSettings('indices', changedFields);
|
||||
const templatesPromise = retrieveSettings('templates', changedFields);
|
||||
// Retrieve all selected settings by default.
|
||||
function retrieveAutoCompleteInfo(settingsToRetrieve = settings.getAutocomplete()) {
|
||||
if (pollTimeoutId) {
|
||||
clearTimeout(pollTimeoutId);
|
||||
}
|
||||
|
||||
const mappingPromise = retrieveSettings('fields', settingsToRetrieve);
|
||||
const aliasesPromise = retrieveSettings('indices', settingsToRetrieve);
|
||||
const templatesPromise = retrieveSettings('templates', settingsToRetrieve);
|
||||
|
||||
$.when(mappingPromise, aliasesPromise, templatesPromise)
|
||||
.done((mappings, aliases, templates) => {
|
||||
|
@ -308,26 +322,28 @@ function retrieveAutocompleteInfoFromServer(changedFields) {
|
|||
// Trigger an update event with the mappings, aliases
|
||||
$(mappingObj).trigger('update', [mappingsResponse, aliases[0]]);
|
||||
}
|
||||
|
||||
// Schedule next request.
|
||||
pollTimeoutId = setTimeout(retrieveAutoCompleteInfo, POLL_INTERVAL);
|
||||
});
|
||||
}
|
||||
|
||||
function autocompleteRetriever() {
|
||||
const changedFields = settings.getAutocomplete();
|
||||
retrieveAutocompleteInfoFromServer(changedFields);
|
||||
setTimeout(function () {
|
||||
autocompleteRetriever();
|
||||
}, 60000);
|
||||
function startPolling() {
|
||||
// Technically, we don't need this method and we could just expose retrieveAutoCompleteInfo.
|
||||
// However, we'll want to allow the user to turn polling on and off eventually so we'll leave this
|
||||
// here to support this eventual functionality.
|
||||
retrieveAutoCompleteInfo();
|
||||
}
|
||||
|
||||
export default _.assign(mappingObj, {
|
||||
getFields: getFields,
|
||||
getTemplates: getTemplates,
|
||||
getIndices: getIndices,
|
||||
getTypes: getTypes,
|
||||
loadMappings: loadMappings,
|
||||
loadAliases: loadAliases,
|
||||
expandAliases: expandAliases,
|
||||
clear: clear,
|
||||
startRetrievingAutoCompleteInfo: autocompleteRetriever,
|
||||
retrieveAutoCompleteInfo: retrieveAutocompleteInfoFromServer
|
||||
getFields,
|
||||
getTemplates,
|
||||
getIndices,
|
||||
getTypes,
|
||||
loadMappings,
|
||||
loadAliases,
|
||||
expandAliases,
|
||||
clear,
|
||||
startPolling,
|
||||
retrieveAutoCompleteInfo,
|
||||
});
|
||||
|
|
|
@ -201,7 +201,7 @@ describe('Integration', () => {
|
|||
endpoints: {
|
||||
_search: {
|
||||
methods: ['GET', 'POST'],
|
||||
patterns: ['{indices}/{types}/_search', '{indices}/_search', '_search'],
|
||||
patterns: ['{indices}/_search', '_search'],
|
||||
data_autocomplete_rules: {
|
||||
query: {
|
||||
match_all: {},
|
||||
|
@ -221,19 +221,15 @@ describe('Integration', () => {
|
|||
|
||||
const MAPPING = {
|
||||
index1: {
|
||||
'type1.1': {
|
||||
properties: {
|
||||
'field1.1.1': { type: 'string' },
|
||||
'field1.1.2': { type: 'string' },
|
||||
},
|
||||
properties: {
|
||||
'field1.1.1': { type: 'string' },
|
||||
'field1.1.2': { type: 'string' },
|
||||
},
|
||||
},
|
||||
index2: {
|
||||
'type2.1': {
|
||||
properties: {
|
||||
'field2.1.1': { type: 'string' },
|
||||
'field2.1.2': { type: 'string' },
|
||||
},
|
||||
properties: {
|
||||
'field2.1.1': { type: 'string' },
|
||||
'field2.1.2': { type: 'string' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -710,6 +706,9 @@ describe('Integration', () => {
|
|||
]
|
||||
);
|
||||
|
||||
// NOTE: This test emits "error while getting completion terms Error: failed to resolve link
|
||||
// [GLOBAL.broken]: Error: failed to resolve global components for ['broken']". but that's
|
||||
// expected.
|
||||
contextTests(
|
||||
{
|
||||
a: {
|
||||
|
@ -980,6 +979,7 @@ describe('Integration', () => {
|
|||
]
|
||||
);
|
||||
|
||||
// NOTE: This test emits "Can't extract a valid url token path", but that's expected.
|
||||
contextTests('POST _search\n', MAPPING, SEARCH_KB, null, [
|
||||
{
|
||||
name: 'initial doc start',
|
||||
|
@ -1014,7 +1014,7 @@ describe('Integration', () => {
|
|||
const CLUSTER_KB = {
|
||||
endpoints: {
|
||||
_search: {
|
||||
patterns: ['_search', '{indices}/{types}/_search', '{indices}/_search'],
|
||||
patterns: ['_search', '{indices}/_search'],
|
||||
url_params: {
|
||||
search_type: ['count', 'query_then_fetch'],
|
||||
scroll: '10m',
|
||||
|
|
|
@ -47,23 +47,21 @@ describe('Mappings', () => {
|
|||
test('Multi fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
tweet: {
|
||||
properties: {
|
||||
first_name: {
|
||||
type: 'multi_field',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
first_name: { type: 'string', index: 'analyzed' },
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
properties: {
|
||||
first_name: {
|
||||
type: 'multi_field',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
first_name: { type: 'string', index: 'analyzed' },
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
last_name: {
|
||||
type: 'multi_field',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
last_name: { type: 'string', index: 'analyzed' },
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
last_name: {
|
||||
type: 'multi_field',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
last_name: { type: 'string', index: 'analyzed' },
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -80,22 +78,20 @@ describe('Mappings', () => {
|
|||
test('Multi fields 1.0 style', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
tweet: {
|
||||
properties: {
|
||||
first_name: {
|
||||
type: 'string',
|
||||
index: 'analyzed',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
properties: {
|
||||
first_name: {
|
||||
type: 'string',
|
||||
index: 'analyzed',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
last_name: {
|
||||
type: 'string',
|
||||
index: 'no',
|
||||
fields: {
|
||||
raw: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
last_name: {
|
||||
type: 'string',
|
||||
index: 'no',
|
||||
fields: {
|
||||
raw: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -113,7 +109,27 @@ describe('Mappings', () => {
|
|||
test('Simple fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
tweet: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
number: {
|
||||
type: 'int',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index').sort(fc)).toEqual([
|
||||
f('number', 'int'),
|
||||
f('str', 'string'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Simple fields - 1.0 style', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
mappings: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
|
@ -132,54 +148,28 @@ describe('Mappings', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('Simple fields - 1.0 style', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
mappings: {
|
||||
tweet: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
number: {
|
||||
type: 'int',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index').sort(fc)).toEqual([
|
||||
f('number', 'int'),
|
||||
f('str', 'string'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Nested fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
tweet: {
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
properties: {
|
||||
first_name: { type: 'string' },
|
||||
last_name: { type: 'string' },
|
||||
},
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
properties: {
|
||||
first_name: { type: 'string' },
|
||||
last_name: { type: 'string' },
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index', ['tweet']).sort(fc)).toEqual([
|
||||
expect(mappings.getFields('index', []).sort(fc)).toEqual([
|
||||
f('message'),
|
||||
f('person.name.first_name'),
|
||||
f('person.name.last_name'),
|
||||
|
@ -190,25 +180,23 @@ describe('Mappings', () => {
|
|||
test('Enabled fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
tweet: {
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'object',
|
||||
enabled: false,
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'object',
|
||||
enabled: false,
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index', ['tweet']).sort(fc)).toEqual([
|
||||
expect(mappings.getFields('index', []).sort(fc)).toEqual([
|
||||
f('message'),
|
||||
f('person.sid'),
|
||||
]);
|
||||
|
@ -217,23 +205,21 @@ describe('Mappings', () => {
|
|||
test('Path tests', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
person: {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'object',
|
||||
path: 'just_name',
|
||||
properties: {
|
||||
first1: { type: 'string' },
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'object',
|
||||
path: 'just_name',
|
||||
properties: {
|
||||
first1: { type: 'string' },
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
name2: {
|
||||
type: 'object',
|
||||
path: 'full',
|
||||
properties: {
|
||||
first2: { type: 'string' },
|
||||
last2: { type: 'string', index_name: 'i_last_2' },
|
||||
},
|
||||
},
|
||||
name2: {
|
||||
type: 'object',
|
||||
path: 'full',
|
||||
properties: {
|
||||
first2: { type: 'string' },
|
||||
last2: { type: 'string', index_name: 'i_last_2' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -251,10 +237,8 @@ describe('Mappings', () => {
|
|||
test('Use index_name tests', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
person: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -284,17 +268,13 @@ describe('Mappings', () => {
|
|||
});
|
||||
mappings.loadMappings({
|
||||
test_index1: {
|
||||
type1: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
test_index2: {
|
||||
type2: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue