mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[context view] Use _doc for tie-breaking instead of _uid (#12096)
Using fields with docvalues (like `_doc`) for tie-breaking yields significantly better performance than using `_uid`, which lacks docvalues at the moment. The downside is that sorting by `_doc` by default is not stable under all conditions, but better than no tie-breaking at all. The new setting `context:tieBreakingFields` enables the user to customize the list of fields Kibana attempts to use for tie-breaking. The first field from that list, that is sortable in the current index pattern, will be used. It defaults to `_doc`, which should change to `_seq_no` from version 6.0 on. In addition to just showing a notification, errors that occur while loading documents from the database will be stored as part of the `loadingStatus` along with a reason code (if known). This is used to display more nuanced and helpful error messages to the user. The first such error message indicates a missing or invalid tiebreaker field required for sorting the context.
This commit is contained in:
parent
a271d7c935
commit
a2727ececf
12 changed files with 143 additions and 50 deletions
|
@ -92,3 +92,4 @@ Markdown.
|
|||
`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the URL, which can lead to problems when there is a lot of information there and the URL gets very long. Enabling this will store parts of the state in your browser session instead, to keep the URL shorter.
|
||||
`context:defaultSize`:: Specifies the initial number of surrounding entries to display in the context view. The default value is 5.
|
||||
`context:step`:: Specifies the number to increment or decrement the context size by when using the buttons in the context view. The default value is 5.
|
||||
`context:tieBreakerFields`:: A comma-separated list of fields to use for tiebreaking between documents that have the same timestamp value. From this list the first field that is present and sortable in the current index pattern is used.
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('context app', function () {
|
|||
it('should use the `fetch` method of the SearchSource', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
expect(searchSourceStub.fetch.calledOnce).to.be(true);
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe('context app', function () {
|
|||
it('should configure the SearchSource to not inherit from the implicit root', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
const inheritsSpy = searchSourceStub.inherits;
|
||||
expect(inheritsSpy.calledOnce).to.be(true);
|
||||
|
@ -50,7 +50,7 @@ describe('context app', function () {
|
|||
it('should set the SearchSource index pattern', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
const setIndexSpy = searchSourceStub.set.withArgs('index');
|
||||
expect(setIndexSpy.calledOnce).to.be(true);
|
||||
|
@ -61,7 +61,7 @@ describe('context app', function () {
|
|||
it('should set the SearchSource version flag to true', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
const setVersionSpy = searchSourceStub.set.withArgs('version');
|
||||
expect(setVersionSpy.calledOnce).to.be(true);
|
||||
|
@ -72,7 +72,7 @@ describe('context app', function () {
|
|||
it('should set the SearchSource size to 1', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
const setSizeSpy = searchSourceStub.set.withArgs('size');
|
||||
expect(setSizeSpy.calledOnce).to.be(true);
|
||||
|
@ -83,7 +83,7 @@ describe('context app', function () {
|
|||
it('should set the SearchSource query to a _uid terms query', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
const setQuerySpy = searchSourceStub.set.withArgs('query');
|
||||
expect(setQuerySpy.calledOnce).to.be(true);
|
||||
|
@ -98,13 +98,13 @@ describe('context app', function () {
|
|||
it('should set the SearchSource sort order', function () {
|
||||
const searchSourceStub = new SearchSourceStub();
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(() => {
|
||||
const setSortSpy = searchSourceStub.set.withArgs('sort');
|
||||
expect(setSortSpy.calledOnce).to.be(true);
|
||||
expect(setSortSpy.firstCall.args[1]).to.eql([
|
||||
{ '@timestamp': 'desc' },
|
||||
{ '_uid': 'asc' },
|
||||
{ '_doc': 'asc' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -113,7 +113,7 @@ describe('context app', function () {
|
|||
const searchSourceStub = new SearchSourceStub();
|
||||
searchSourceStub._stubHits = [];
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then(
|
||||
() => {
|
||||
expect().fail('expected the promise to be rejected');
|
||||
|
@ -131,7 +131,7 @@ describe('context app', function () {
|
|||
{ property2: 'value2' },
|
||||
];
|
||||
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
|
||||
return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }])
|
||||
.then((anchorDocument) => {
|
||||
expect(anchorDocument).to.have.property('property1', 'value1');
|
||||
expect(anchorDocument).to.have.property('$$_isAnchor', true);
|
||||
|
|
|
@ -19,7 +19,7 @@ function fetchAnchorProvider(courier, Private) {
|
|||
_uid: [uid],
|
||||
},
|
||||
})
|
||||
.set('sort', [sort, { _uid: 'asc' }]);
|
||||
.set('sort', sort);
|
||||
|
||||
const response = await searchSource.fetch();
|
||||
|
||||
|
|
|
@ -14,11 +14,10 @@ function fetchContextProvider(courier, Private) {
|
|||
};
|
||||
|
||||
async function fetchSuccessors(indexPatternId, anchorDocument, contextSort, size, filters) {
|
||||
const successorsSort = [contextSort, { _uid: 'asc' }];
|
||||
const successorsSearchSource = await createSearchSource(
|
||||
indexPatternId,
|
||||
anchorDocument,
|
||||
successorsSort,
|
||||
contextSort,
|
||||
size,
|
||||
filters,
|
||||
);
|
||||
|
@ -27,7 +26,7 @@ function fetchContextProvider(courier, Private) {
|
|||
}
|
||||
|
||||
async function fetchPredecessors(indexPatternId, anchorDocument, contextSort, size, filters) {
|
||||
const predecessorsSort = [reverseSortDirective(contextSort), { _uid: 'desc' }];
|
||||
const predecessorsSort = contextSort.map(reverseSortDirective);
|
||||
const predecessorsSearchSource = await createSearchSource(
|
||||
indexPatternId,
|
||||
anchorDocument,
|
||||
|
|
|
@ -1,5 +1,33 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
|
||||
/**
|
||||
* The list of field names that are allowed for sorting, but not included in
|
||||
* index pattern fields.
|
||||
*
|
||||
* @constant
|
||||
* @type {string[]}
|
||||
*/
|
||||
const META_FIELD_NAMES = ['_seq_no', '_doc', '_uid'];
|
||||
|
||||
/**
|
||||
* Returns a field from the intersection of the set of sortable fields in the
|
||||
* given index pattern and a given set of candidate field names.
|
||||
*
|
||||
* @param {IndexPattern} indexPattern - The index pattern to search for
|
||||
* sortable fields
|
||||
* @param {string[]} fields - The list of candidate field names
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getFirstSortableField(indexPattern, fieldNames) {
|
||||
const sortableFields = fieldNames.filter((fieldName) => (
|
||||
META_FIELD_NAMES.includes(fieldName)
|
||||
|| (indexPattern.fields.byName[fieldName] || { sortable: false }).sortable
|
||||
));
|
||||
return sortableFields[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* A sort directive in object or string form.
|
||||
*
|
||||
|
@ -72,6 +100,7 @@ function reverseSortDirection(sortDirection) {
|
|||
|
||||
|
||||
export {
|
||||
getFirstSortableField,
|
||||
reverseSortDirection,
|
||||
reverseSortDirective,
|
||||
};
|
||||
|
|
|
@ -18,19 +18,33 @@
|
|||
<!-- Error feedback -->
|
||||
<div
|
||||
class="kuiViewContent kuiViewContentItem"
|
||||
ng-if="contextApp.state.loadingStatus.anchor === contextApp.constants.LOADING_STATUS.FAILED"
|
||||
ng-if="contextApp.state.loadingStatus.anchor.status === contextApp.constants.LOADING_STATUS.FAILED"
|
||||
>
|
||||
<div class="kuiInfoPanel kuiInfoPanel--error kuiVerticalRhythm">
|
||||
<div class="kuiInfoPanelHeader">
|
||||
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>
|
||||
<span class="kuiInfoPanelHeader__title">
|
||||
Problem with query
|
||||
Failed to load the anchor document
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="kuiInfoPanelBody">
|
||||
<div class="kuiInfoPanelBody__message">
|
||||
Failed to load the anchor document. Please reload or visit
|
||||
<div
|
||||
class="kuiInfoPanelBody__message"
|
||||
ng-if="contextApp.state.loadingStatus.anchor.reason === contextApp.constants.FAILURE_REASONS.INVALID_TIEBREAKER"
|
||||
>
|
||||
No searchable tiebreaker field could be found in the index pattern
|
||||
{{ contextApp.state.queryParameters.indexPatternId}}.
|
||||
|
||||
Please change the advanced setting
|
||||
<code>context:tieBreakerFields</code> to include a valid field for this
|
||||
index pattern.
|
||||
</div>
|
||||
<div
|
||||
class="kuiInfoPanelBody__message"
|
||||
ng-if="contextApp.state.loadingStatus.anchor.reason === contextApp.constants.FAILURE_REASONS.UNKNOWN"
|
||||
>
|
||||
Please reload or visit
|
||||
<a ng-href="{{ contextApp.state.navigation.discover.url }}">Discover</a>
|
||||
to select a valid anchor document.
|
||||
</div>
|
||||
|
@ -40,7 +54,7 @@
|
|||
|
||||
<div
|
||||
class="kuiViewContent kuiViewContentItem"
|
||||
ng-if="contextApp.state.loadingStatus.anchor !== contextApp.constants.LOADING_STATUS.FAILED"
|
||||
ng-if="contextApp.state.loadingStatus.anchor.status !== contextApp.constants.LOADING_STATUS.FAILED"
|
||||
role="main"
|
||||
>
|
||||
<!-- Controls -->
|
||||
|
@ -51,7 +65,7 @@
|
|||
is-disabled="![
|
||||
contextApp.constants.LOADING_STATUS.LOADED,
|
||||
contextApp.constants.LOADING_STATUS.FAILED,
|
||||
].includes(contextApp.state.loadingStatus.predecessors)"
|
||||
].includes(contextApp.state.loadingStatus.predecessors.status)"
|
||||
icon="'fa-chevron-up'"
|
||||
ng-click="contextApp.actions.fetchMorePredecessorRows()"
|
||||
>
|
||||
|
@ -60,13 +74,13 @@
|
|||
<context-size-picker
|
||||
count="contextApp.state.queryParameters.predecessorCount"
|
||||
data-test-subj="predecessorCountPicker"
|
||||
is-disabled="contextApp.state.loadingStatus.anchor !== contextApp.constants.LOADING_STATUS.LOADED"
|
||||
is-disabled="contextApp.state.loadingStatus.anchor.status !== contextApp.constants.LOADING_STATUS.LOADED"
|
||||
on-change-count="contextApp.actions.fetchGivenPredecessorRows"
|
||||
></context-size-picker>
|
||||
<span>newer documents</span>
|
||||
<span
|
||||
class="kuiStatusText kuiStatusText--warning"
|
||||
ng-if="(contextApp.state.loadingStatus.predecessors === contextApp.constants.LOADING_STATUS.LOADED)
|
||||
ng-if="(contextApp.state.loadingStatus.predecessors.status === contextApp.constants.LOADING_STATUS.LOADED)
|
||||
&& (contextApp.state.queryParameters.predecessorCount > contextApp.state.rows.predecessors.length)"
|
||||
>
|
||||
<span class="kuiStatusText__icon kuiIcon fa-bolt"></span>
|
||||
|
@ -83,7 +97,7 @@
|
|||
ng-if="[
|
||||
contextApp.constants.LOADING_STATUS.UNINITIALIZED,
|
||||
contextApp.constants.LOADING_STATUS.LOADING,
|
||||
].includes(contextApp.state.loadingStatus.anchor)"
|
||||
].includes(contextApp.state.loadingStatus.anchor.status)"
|
||||
class="kuiPanel kuiPanel--centered kuiVerticalRhythm"
|
||||
>
|
||||
<div class="kuiTableInfo">
|
||||
|
@ -93,7 +107,7 @@
|
|||
|
||||
<!-- Table -->
|
||||
<div
|
||||
ng-if="contextApp.state.loadingStatus.anchor === contextApp.constants.LOADING_STATUS.LOADED"
|
||||
ng-if="contextApp.state.loadingStatus.anchor.status === contextApp.constants.LOADING_STATUS.LOADED"
|
||||
class="kuiPanel kuiVerticalRhythm"
|
||||
>
|
||||
<div class="discover-table" fixed-scroll>
|
||||
|
@ -116,7 +130,7 @@
|
|||
is-disabled="![
|
||||
contextApp.constants.LOADING_STATUS.LOADED,
|
||||
contextApp.constants.LOADING_STATUS.FAILED,
|
||||
].includes(contextApp.state.loadingStatus.successors)"
|
||||
].includes(contextApp.state.loadingStatus.successors.status)"
|
||||
icon="'fa-chevron-down'"
|
||||
ng-click="contextApp.actions.fetchMoreSuccessorRows()"
|
||||
>
|
||||
|
@ -125,13 +139,13 @@
|
|||
<context-size-picker
|
||||
count="contextApp.state.queryParameters.successorCount"
|
||||
data-test-subj="successorCountPicker"
|
||||
is-disabled="contextApp.state.loadingStatus.anchor !== contextApp.constants.LOADING_STATUS.LOADED"
|
||||
is-disabled="contextApp.state.loadingStatus.anchor.status !== contextApp.constants.LOADING_STATUS.LOADED"
|
||||
on-change-count="contextApp.actions.fetchGivenSuccessorRows"
|
||||
></context-size-picker>
|
||||
<div>older documents</div>
|
||||
<span
|
||||
class="kuiStatusText kuiStatusText--warning"
|
||||
ng-if="(contextApp.state.loadingStatus.successors === contextApp.constants.LOADING_STATUS.LOADED)
|
||||
ng-if="(contextApp.state.loadingStatus.successors.status === contextApp.constants.LOADING_STATUS.LOADED)
|
||||
&& (contextApp.state.queryParameters.successorCount > contextApp.state.rows.successors.length)"
|
||||
>
|
||||
<span class="kuiStatusText__icon kuiIcon fa-bolt"></span>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { uiModules } from 'ui/modules';
|
|||
import contextAppTemplate from './app.html';
|
||||
import './components/loading_button';
|
||||
import './components/size_picker/size_picker';
|
||||
import { getFirstSortableField } from './api/utils/sorting';
|
||||
import {
|
||||
createInitialQueryParametersState,
|
||||
QueryParameterActionsProvider,
|
||||
|
@ -11,6 +12,7 @@ import {
|
|||
} from './query_parameters';
|
||||
import {
|
||||
createInitialLoadingStatusState,
|
||||
FAILURE_REASONS,
|
||||
LOADING_STATUS,
|
||||
QueryActionsProvider,
|
||||
} from './query';
|
||||
|
@ -52,6 +54,7 @@ function ContextAppController($scope, config, Private, timefilter) {
|
|||
|
||||
this.state = createInitialState(
|
||||
parseInt(config.get('context:step'), 10),
|
||||
getFirstSortableField(this.indexPattern, config.get('context:tieBreakerFields')),
|
||||
this.discoverUrl,
|
||||
);
|
||||
|
||||
|
@ -62,6 +65,7 @@ function ContextAppController($scope, config, Private, timefilter) {
|
|||
), (action) => (...args) => action(this.state)(...args));
|
||||
|
||||
this.constants = {
|
||||
FAILURE_REASONS,
|
||||
LOADING_STATUS,
|
||||
};
|
||||
|
||||
|
@ -111,9 +115,9 @@ function ContextAppController($scope, config, Private, timefilter) {
|
|||
);
|
||||
}
|
||||
|
||||
function createInitialState(defaultStepSize, discoverUrl) {
|
||||
function createInitialState(defaultStepSize, tieBreakerField, discoverUrl) {
|
||||
return {
|
||||
queryParameters: createInitialQueryParametersState(defaultStepSize),
|
||||
queryParameters: createInitialQueryParametersState(defaultStepSize, tieBreakerField),
|
||||
rows: {
|
||||
all: [],
|
||||
anchor: null,
|
||||
|
|
|
@ -3,7 +3,7 @@ import _ from 'lodash';
|
|||
import { fetchAnchorProvider } from '../api/anchor';
|
||||
import { fetchContextProvider } from '../api/context';
|
||||
import { QueryParameterActionsProvider } from '../query_parameters';
|
||||
import { LOADING_STATUS } from './constants';
|
||||
import { FAILURE_REASONS, LOADING_STATUS } from './constants';
|
||||
|
||||
|
||||
export function QueryActionsProvider(courier, Notifier, Private, Promise) {
|
||||
|
@ -21,26 +21,48 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
|
|||
location: 'Context',
|
||||
});
|
||||
|
||||
const setLoadingStatus = (state) => (subject, status) => (
|
||||
state.loadingStatus[subject] = status
|
||||
const setFailedStatus = (state) => (subject, details = {}) => (
|
||||
state.loadingStatus[subject] = {
|
||||
status: LOADING_STATUS.FAILED,
|
||||
reason: FAILURE_REASONS.UNKNOWN,
|
||||
...details,
|
||||
}
|
||||
);
|
||||
|
||||
const setLoadedStatus = (state) => (subject) => (
|
||||
state.loadingStatus[subject] = {
|
||||
status: LOADING_STATUS.LOADED,
|
||||
}
|
||||
);
|
||||
|
||||
const setLoadingStatus = (state) => (subject) => (
|
||||
state.loadingStatus[subject] = {
|
||||
status: LOADING_STATUS.LOADING,
|
||||
}
|
||||
);
|
||||
|
||||
const fetchAnchorRow = (state) => () => {
|
||||
const { queryParameters: { indexPatternId, anchorUid, sort } } = state;
|
||||
const { queryParameters: { indexPatternId, anchorUid, sort, tieBreakerField } } = state;
|
||||
|
||||
setLoadingStatus(state)('anchor', LOADING_STATUS.LOADING);
|
||||
if (!tieBreakerField) {
|
||||
return Promise.reject(setFailedStatus(state)('anchor', {
|
||||
reason: FAILURE_REASONS.INVALID_TIEBREAKER
|
||||
}));
|
||||
}
|
||||
|
||||
setLoadingStatus(state)('anchor');
|
||||
|
||||
return Promise.try(() => (
|
||||
fetchAnchor(indexPatternId, anchorUid, _.zipObject([sort]))
|
||||
fetchAnchor(indexPatternId, anchorUid, [_.zipObject([sort]), { [tieBreakerField]: 'asc' }])
|
||||
))
|
||||
.then(
|
||||
(anchorDocument) => {
|
||||
setLoadingStatus(state)('anchor', LOADING_STATUS.LOADED);
|
||||
setLoadedStatus(state)('anchor');
|
||||
state.rows.anchor = anchorDocument;
|
||||
return anchorDocument;
|
||||
},
|
||||
(error) => {
|
||||
setLoadingStatus(state)('anchor', LOADING_STATUS.FAILED);
|
||||
setFailedStatus(state)('anchor', { error });
|
||||
notifier.error(error);
|
||||
throw error;
|
||||
}
|
||||
|
@ -49,23 +71,29 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
|
|||
|
||||
const fetchPredecessorRows = (state) => () => {
|
||||
const {
|
||||
queryParameters: { indexPatternId, filters, predecessorCount, sort },
|
||||
queryParameters: { indexPatternId, filters, predecessorCount, sort, tieBreakerField },
|
||||
rows: { anchor },
|
||||
} = state;
|
||||
|
||||
setLoadingStatus(state)('predecessors', LOADING_STATUS.LOADING);
|
||||
if (!tieBreakerField) {
|
||||
return Promise.reject(setFailedStatus(state)('predecessors', {
|
||||
reason: FAILURE_REASONS.INVALID_TIEBREAKER
|
||||
}));
|
||||
}
|
||||
|
||||
setLoadingStatus(state)('predecessors');
|
||||
|
||||
return Promise.try(() => (
|
||||
fetchPredecessors(indexPatternId, anchor, _.zipObject([sort]), predecessorCount, filters)
|
||||
fetchPredecessors(indexPatternId, anchor, [_.zipObject([sort]), { [tieBreakerField]: 'asc' }], predecessorCount, filters)
|
||||
))
|
||||
.then(
|
||||
(predecessorDocuments) => {
|
||||
setLoadingStatus(state)('predecessors', LOADING_STATUS.LOADED);
|
||||
setLoadedStatus(state)('predecessors');
|
||||
state.rows.predecessors = predecessorDocuments;
|
||||
return predecessorDocuments;
|
||||
},
|
||||
(error) => {
|
||||
setLoadingStatus(state)('predecessors', LOADING_STATUS.FAILED);
|
||||
setFailedStatus(state)('predecessors', { error });
|
||||
notifier.error(error);
|
||||
throw error;
|
||||
},
|
||||
|
@ -74,23 +102,29 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
|
|||
|
||||
const fetchSuccessorRows = (state) => () => {
|
||||
const {
|
||||
queryParameters: { indexPatternId, filters, sort, successorCount },
|
||||
queryParameters: { indexPatternId, filters, sort, successorCount, tieBreakerField },
|
||||
rows: { anchor },
|
||||
} = state;
|
||||
|
||||
setLoadingStatus(state)('successors', LOADING_STATUS.LOADING);
|
||||
if (!tieBreakerField) {
|
||||
return Promise.reject(setFailedStatus(state)('successors', {
|
||||
reason: FAILURE_REASONS.INVALID_TIEBREAKER
|
||||
}));
|
||||
}
|
||||
|
||||
setLoadingStatus(state)('successors');
|
||||
|
||||
return Promise.try(() => (
|
||||
fetchSuccessors(indexPatternId, anchor, _.zipObject([sort]), successorCount, filters)
|
||||
fetchSuccessors(indexPatternId, anchor, [_.zipObject([sort]), { [tieBreakerField]: 'asc' }], successorCount, filters)
|
||||
))
|
||||
.then(
|
||||
(successorDocuments) => {
|
||||
setLoadingStatus(state)('successors', LOADING_STATUS.LOADED);
|
||||
setLoadedStatus(state)('successors');
|
||||
state.rows.successors = successorDocuments;
|
||||
return successorDocuments;
|
||||
},
|
||||
(error) => {
|
||||
setLoadingStatus(state)('successors', LOADING_STATUS.FAILED);
|
||||
setFailedStatus(state)('successors', { error });
|
||||
notifier.error(error);
|
||||
throw error;
|
||||
},
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
export const FAILURE_REASONS = {
|
||||
UNKNOWN: 'unknown',
|
||||
INVALID_TIEBREAKER: 'invalid_tiebreaker',
|
||||
};
|
||||
|
||||
export const LOADING_STATUS = {
|
||||
FAILED: 'failed',
|
||||
LOADED: 'loaded',
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export { QueryActionsProvider } from './actions';
|
||||
export { LOADING_STATUS } from './constants';
|
||||
export { FAILURE_REASONS, LOADING_STATUS } from './constants';
|
||||
export { createInitialLoadingStatusState } from './state';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function createInitialQueryParametersState(defaultStepSize) {
|
||||
export function createInitialQueryParametersState(defaultStepSize, tieBreakerField) {
|
||||
return {
|
||||
anchorUid: null,
|
||||
columns: [],
|
||||
|
@ -8,5 +8,6 @@ export function createInitialQueryParametersState(defaultStepSize) {
|
|||
predecessorCount: 0,
|
||||
successorCount: 0,
|
||||
sort: [],
|
||||
tieBreakerField,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -341,6 +341,12 @@ export function getDefaultSettings() {
|
|||
'context:step': {
|
||||
value: 5,
|
||||
description: 'The step size to increment or decrement the context size by',
|
||||
}
|
||||
},
|
||||
'context:tieBreakerFields': {
|
||||
value: ['_doc'],
|
||||
description: 'A comma-separated list of fields to use for tiebreaking between documents ' +
|
||||
'that have the same timestamp value. From this list the first field that ' +
|
||||
'is present and sortable in the current index pattern is used.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue