@@ -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 @@ newer documents contextApp.state.rows.predecessors.length)" > @@ -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" >
@@ -93,7 +107,7 @@
@@ -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 @@
older documents
contextApp.state.rows.successors.length)" > diff --git a/src/core_plugins/kibana/public/context/app.js b/src/core_plugins/kibana/public/context/app.js index 0612d4f300ee4..538c1d7909496 100644 --- a/src/core_plugins/kibana/public/context/app.js +++ b/src/core_plugins/kibana/public/context/app.js @@ -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, diff --git a/src/core_plugins/kibana/public/context/query/actions.js b/src/core_plugins/kibana/public/context/query/actions.js index e05b6c00c75c6..d0660bbc77838 100644 --- a/src/core_plugins/kibana/public/context/query/actions.js +++ b/src/core_plugins/kibana/public/context/query/actions.js @@ -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; }, diff --git a/src/core_plugins/kibana/public/context/query/constants.js b/src/core_plugins/kibana/public/context/query/constants.js index 4d63357f105ba..e2f51765d2e9f 100644 --- a/src/core_plugins/kibana/public/context/query/constants.js +++ b/src/core_plugins/kibana/public/context/query/constants.js @@ -1,3 +1,8 @@ +export const FAILURE_REASONS = { + UNKNOWN: 'unknown', + INVALID_TIEBREAKER: 'invalid_tiebreaker', +}; + export const LOADING_STATUS = { FAILED: 'failed', LOADED: 'loaded', diff --git a/src/core_plugins/kibana/public/context/query/index.js b/src/core_plugins/kibana/public/context/query/index.js index e0231e236fae4..ced9dfb6b0796 100644 --- a/src/core_plugins/kibana/public/context/query/index.js +++ b/src/core_plugins/kibana/public/context/query/index.js @@ -1,3 +1,3 @@ export { QueryActionsProvider } from './actions'; -export { LOADING_STATUS } from './constants'; +export { FAILURE_REASONS, LOADING_STATUS } from './constants'; export { createInitialLoadingStatusState } from './state'; diff --git a/src/core_plugins/kibana/public/context/query_parameters/state.js b/src/core_plugins/kibana/public/context/query_parameters/state.js index 5b9023e209a30..78b6819ba4626 100644 --- a/src/core_plugins/kibana/public/context/query_parameters/state.js +++ b/src/core_plugins/kibana/public/context/query_parameters/state.js @@ -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, }; } diff --git a/src/ui/ui_settings/defaults.js b/src/ui/ui_settings/defaults.js index 20095c9e26aa7..10f383a1603d2 100644 --- a/src/ui/ui_settings/defaults.js +++ b/src/ui/ui_settings/defaults.js @@ -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.', + }, }; }