-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
Copy pathsearch_source.js
309 lines (279 loc) · 10.3 KB
/
search_source.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/**
* @name SearchSource
*
* @description A promise-based stream of search results that can inherit from other search sources.
*
* Because filters/queries in Kibana have different levels of persistence and come from different
* places, it is important to keep track of where filters come from for when they are saved back to
* the savedObject store in the Kibana index. To do this, we create trees of searchSource objects
* that can have associated query parameters (index, query, filter, etc) which can also inherit from
* other searchSource objects.
*
* At query time, all of the searchSource objects that have subscribers are "flattened", at which
* point the query params from the searchSource are collected while traversing up the inheritance
* chain. At each link in the chain a decision about how to merge the query params is made until a
* single set of query parameters is created for each active searchSource (a searchSource with
* subscribers).
*
* That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy
* works in Kibana.
*
* Visualize, starting from a new search:
*
* - the `savedVis.searchSource` is set as the `appSearchSource`.
* - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is
* upgraded to inherit from the `rootSearchSource`.
* - Any interaction with the visualization will still apply filters to the `appSearchSource`, so
* they will be stored directly on the `savedVis.searchSource`.
* - Any interaction with the time filter will be written to the `rootSearchSource`, so those
* filters will not be saved by the `savedVis`.
* - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are
* defined on it directly, but none of the ones that it inherits from other places.
*
* Visualize, starting from an existing search:
*
* - The `savedVis` loads the `savedSearch` on which it is built.
* - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as
* the `appSearchSource`.
* - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`.
* - Then the `savedVis` is written to elasticsearch it will be flattened and only include the
* filters created in the visualize application and will reconnect the filters from the
* `savedSearch` at runtime to prevent losing the relationship
*
* Dashboard search sources:
*
* - Each panel in a dashboard has a search source.
* - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`.
* - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from
* the dashboard search source.
* - When a filter is added to the search box, or via a visualization, it is written to the
* `appSearchSource`.
*/
import _ from 'lodash';
import { NormalizeSortRequestProvider } from './_normalize_sort_request';
import { RootSearchSourceProvider } from './_root_search_source';
import { AbstractDataSourceProvider } from './_abstract';
import { SearchRequestProvider } from '../fetch/request/search';
import { SegmentedRequestProvider } from '../fetch/request/segmented';
import { SearchStrategyProvider } from '../fetch/strategy/search';
export function SearchSourceProvider(Promise, Private, config) {
const SourceAbstract = Private(AbstractDataSourceProvider);
const SearchRequest = Private(SearchRequestProvider);
const SegmentedRequest = Private(SegmentedRequestProvider);
const searchStrategy = Private(SearchStrategyProvider);
const normalizeSortRequest = Private(NormalizeSortRequestProvider);
const forIp = Symbol('for which index pattern?');
function isIndexPattern(val) {
return Boolean(val && typeof val.toIndexList === 'function');
}
_.class(SearchSource).inherits(SourceAbstract);
function SearchSource(initialState) {
SearchSource.Super.call(this, initialState, searchStrategy);
}
/*****
* PUBLIC API
*****/
/**
* List of the editable state properties that turn into a
* chainable API
*
* @type {Array}
*/
SearchSource.prototype._methods = [
'type',
'query',
'filter',
'sort',
'highlight',
'highlightAll',
'aggs',
'from',
'searchAfter',
'size',
'source',
'version',
'fields',
];
SearchSource.prototype.index = function (indexPattern) {
const state = this._state;
const hasSource = state.source;
const sourceCameFromIp = hasSource && state.source.hasOwnProperty(forIp);
const sourceIsForOurIp = sourceCameFromIp && state.source[forIp] === state.index;
if (sourceIsForOurIp) {
delete state.source;
}
if (indexPattern === undefined) return state.index;
if (indexPattern === null) return delete state.index;
if (!isIndexPattern(indexPattern)) {
throw new TypeError('expected indexPattern to be an IndexPattern duck.');
}
state.index = indexPattern;
if (!state.source) {
// imply source filtering based on the index pattern, but allow overriding
// it by simply setting another value for "source". When index is changed
state.source = function () {
return indexPattern.getSourceFiltering();
};
state.source[forIp] = indexPattern;
}
return this;
};
SearchSource.prototype.extend = function () {
return (new SearchSource()).inherits(this);
};
/**
* Set a searchSource that this source should inherit from
* @param {SearchSource} searchSource - the parent searchSource
* @return {this} - chainable
*/
SearchSource.prototype.inherits = function (parent) {
this._parent = parent;
return this;
};
/**
* Get the parent of this SearchSource
* @return {undefined|searchSource}
*/
SearchSource.prototype.getParent = function (onlyHardLinked) {
const self = this;
if (self._parent === false) return;
if (self._parent) return self._parent;
return onlyHardLinked || this.skipTimeRangeFilter ? undefined : Private(RootSearchSourceProvider).get();
};
/**
* Temporarily prevent this Search from being fetched... not a fan but it's easy
*/
SearchSource.prototype.disable = function () {
this._fetchDisabled = true;
};
/**
* Reverse of SourceAbstract#disable(), only need to call this if source was previously disabled
*/
SearchSource.prototype.enable = function () {
this._fetchDisabled = false;
};
SearchSource.prototype.onBeginSegmentedFetch = function (initFunction) {
const self = this;
return Promise.try(function addRequest() {
const req = new SegmentedRequest(self, Promise.defer(), initFunction);
// return promises created by the completion handler so that
// errors will bubble properly
return req.getCompletePromise().then(addRequest);
});
};
/******
* PRIVATE APIS
******/
/**
* Gets the type of the DataSource
* @return {string}
*/
SearchSource.prototype._getType = function () {
return 'search';
};
/**
* Create a common search request object, which should
* be put into the pending request queye, for this search
* source
*
* @param {Deferred} defer - the deferred object that should be resolved
* when the request is complete
* @return {SearchRequest}
*/
SearchSource.prototype._createRequest = function (defer) {
return new SearchRequest(this, defer);
};
/**
* Used to merge properties into the state within ._flatten().
* The state is passed in and modified by the function
*
* @param {object} state - the current merged state
* @param {*} val - the value at `key`
* @param {*} key - The key of `val`
* @return {undefined}
*/
SearchSource.prototype._mergeProp = function (state, val, key) {
if (typeof val === 'function') {
const source = this;
return Promise.cast(val(this))
.then(function (newVal) {
return source._mergeProp(state, newVal, key);
});
}
if (val == null || !key || !_.isString(key)) return;
switch (key) {
case 'filter':
let verifiedFilters = val;
if (config.get('courier:ignoreFilterIfFieldNotInIndex')) {
if (!Array.isArray(val)) val = [val];
verifiedFilters = val.filter(function (el) {
if ('meta' in el && 'index' in state) {
const field = state.index.fields.byName[el.meta.key];
if (!field) return false;
}
return true;
});
}
// user a shallow flatten to detect if val is an array, and pull the values out if it is
state.filters = _([ state.filters || [], verifiedFilters ])
.flatten()
// Yo Dawg! I heard you needed to filter out your filters
.reject(function (filter) {
return !filter || _.get(filter, 'meta.disabled');
})
.value();
return;
case 'index':
case 'type':
case 'id':
case 'highlightAll':
if (key && state[key] == null) {
state[key] = val;
}
return;
case 'searchAfter':
key = 'search_after';
addToBody();
break;
case 'source':
key = '_source';
addToBody();
break;
case 'sort':
val = normalizeSortRequest(val, this.get('index'));
addToBody();
break;
case 'query':
state.query = (state.query || []).concat(val);
break;
case 'fields':
state[key] = _.uniq([...(state[key] || []), ...val]);
break;
default:
addToBody();
}
/**
* Add the key and val to the body of the request
*/
function addToBody() {
state.body = state.body || {};
// ignore if we already have a value
if (state.body[key] == null) {
state.body[key] = val;
}
}
};
SearchSource.prototype.clone = function () {
const clone = new SearchSource(this.toString());
// when serializing the internal state with .toString() we lose the internal classes used in the index
// pattern, so we have to set it again to workaround this behavior
clone.set('index', this.get('index'));
clone.inherits(this.getParent());
return clone;
};
SearchSource.prototype.getSearchRequestBody = async function () {
const searchRequest = await this._flatten();
return searchRequest.body;
};
return SearchSource;
}