Date nanos context typescript conversion (#38735)

This commit is contained in:
Matthias Wilhelm 2019-06-14 18:48:08 +02:00 committed by GitHub
parent 5badcbd0cc
commit 2d9975c147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 425 additions and 306 deletions

View file

@ -48,7 +48,25 @@ describe('context app', function () {
getSearchSourceStub = createSearchSourceStubProvider([], '@timestamp', MS_PER_DAY * 8);
Private.stub(SearchSourceProvider, getSearchSourceStub);
fetchPredecessors = Private(fetchContextProvider).fetchPredecessors;
fetchPredecessors = (indexPatternId, timeField, sortDir, timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => {
const anchor = {
_source: {
[timeField]: timeValIso
},
sort: [timeValNr, tieBreakerValue]
};
return Private(fetchContextProvider).fetchSurroundingDocs(
'predecessors',
indexPatternId,
anchor,
timeField,
tieBreakerField,
sortDir,
size,
[]
);
};
}));
it('should perform exactly one query when enough hits are returned', function () {

View file

@ -47,7 +47,25 @@ describe('context app', function () {
getSearchSourceStub = createSearchSourceStubProvider([], '@timestamp');
Private.stub(SearchSourceProvider, getSearchSourceStub);
fetchSuccessors = Private(fetchContextProvider).fetchSuccessors;
fetchSuccessors = (indexPatternId, timeField, sortDir, timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => {
const anchor = {
_source: {
[timeField]: timeValIso
},
sort: [timeValNr, tieBreakerValue]
};
return Private(fetchContextProvider).fetchSurroundingDocs(
'successors',
indexPatternId,
anchor,
timeField,
tieBreakerField,
sortDir,
size,
[]
);
};
}));
it('should perform exactly one query when enough hits are returned', function () {

View file

@ -1,251 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-check
// @ts-ignore
import { SearchSourceProvider } from 'ui/courier';
import { reverseSortDirection } from './utils/sorting';
import {
extractNanoSeconds,
convertIsoToNanosAsStr,
convertIsoToMillis,
convertTimeValueToIso
} from './utils/date_conversion';
/**
* @typedef {Object} SearchResult
* @prop {{ total: number, hits: any[] }} hits
* @prop {Object} aggregations
*/
/**
* @typedef {Object} SearchSourceT
* @prop {function(): Promise<SearchResult>} fetch
* @prop {function(string, any): SearchSourceT} setField
* @prop {function(any): SearchSourceT} setParent
*/
/**
* @typedef {'asc' | 'desc'} SortDirection
*/
/**
* @typedef {'successors' |'predecessors'} SurroundingDocType
*/
const DAY_MILLIS = 24 * 60 * 60 * 1000;
// look from 1 day up to 10000 days into the past and future
const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map((days) => days * DAY_MILLIS);
function fetchContextProvider(indexPatterns, Private) {
/**
* @type {{new(): SearchSourceT}}
*/
const SearchSource = Private(SearchSourceProvider);
return {
// @ts-ignore / for testing
fetchPredecessors: (...args) => fetchSurroundingDocs('predecessors', ...args),
// @ts-ignore / for testing
fetchSuccessors: (...args) => fetchSurroundingDocs('successors', ...args),
fetchSurroundingDocs,
};
/**
* Fetch successor or predecessor documents of a given anchor document
*
* @param {SurroundingDocType} type - `successors` or `predecessors`
* @param {string} indexPatternId
* @param {string} timeFieldName - name of the timefield, that's sorted on
* @param {SortDirection} timeFieldSortDir - direction of sorting
* @param {string} timeFieldIsoValue - value of the anchors timefield in ISO format
* @param {number} timeFieldNumValue - value of the anchors timefield in numeric format (invalid for nanos)
* @param {string} tieBreakerField - name of 2nd param for sorting
* @param {string} tieBreakerValue - value of 2nd param for sorting
* @param {number} size - number of records to retrieve
* @param {any[]} filters - to apply in the elastic query
* @returns {Promise<object[]>}
*/
async function fetchSurroundingDocs(
type,
indexPatternId,
timeFieldName,
timeFieldSortDir,
timeFieldIsoValue,
timeFieldNumValue,
tieBreakerField,
tieBreakerValue,
size,
filters
) {
const indexPattern = await indexPatterns.get(indexPatternId);
const searchSource = await createSearchSource(indexPattern, filters);
const sortDir = type === 'successors' ? timeFieldSortDir : reverseSortDirection(timeFieldSortDir);
const nanoSeconds = indexPattern.isTimeNanosBased() ? extractNanoSeconds(timeFieldIsoValue) : '';
const timeValueMillis = nanoSeconds !== '' ? convertIsoToMillis(timeFieldIsoValue) : timeFieldNumValue;
const offsetSign = (timeFieldSortDir === 'asc' && type === 'successors' || timeFieldSortDir === 'desc' && type === 'predecessors')
? 1
: -1;
// ending with `null` opens the last interval
const intervals = asPairs([...LOOKUP_OFFSETS.map(offset => timeValueMillis + offset * offsetSign), null]);
let documents = [];
for (const [iStartTimeValue, iEndTimeValue] of intervals) {
const remainingSize = size - documents.length;
if (remainingSize <= 0) {
break;
}
const afterTimeRecIdx = type === 'successors' && documents.length ? documents.length - 1 : 0;
const afterTimeValue = nanoSeconds
? convertIsoToNanosAsStr(documents.length ? documents[afterTimeRecIdx]._source[timeFieldName] : timeFieldIsoValue)
: timeFieldNumValue;
const afterTieBreakerValue = documents.length > 0 ? documents[afterTimeRecIdx].sort[1] : tieBreakerValue;
const hits = await fetchHitsInInterval(
searchSource,
timeFieldName,
sortDir,
iStartTimeValue,
iEndTimeValue,
afterTimeValue,
tieBreakerField,
afterTieBreakerValue,
remainingSize,
nanoSeconds
);
documents = type === 'successors'
? [...documents, ...hits]
: [...hits.slice().reverse(), ...documents];
}
return documents;
}
/**
* @param {Object} indexPattern
* @param {any[]} filters
* @returns {Promise<Object>}
*/
async function createSearchSource(indexPattern, filters) {
return new SearchSource()
.setParent(false)
.setField('index', indexPattern)
.setField('filter', filters);
}
/**
* Fetch the hits between `(afterTimeValue, tieBreakerValue)` and
* `endRangeMillis` from the `searchSource` using the given `timeField` and
* `tieBreakerField` fields up to a maximum of `maxCount` documents. The
* documents are sorted by `(timeField, tieBreakerField)` using the
* `timeSortDirection` for both fields
*
* The `searchSource` is assumed to have the appropriate index pattern
* and filters set.
*
* @param {SearchSourceT} searchSource
* @param {string} timeFieldName
* @param {SortDirection} timeFieldSortDir
* @param {number} startRangeMillis
* @param {number | null} endRangeMillis
* @param {number| string} afterTimeValue
* @param {string} tieBreakerField
* @param {number} tieBreakerValue
* @param {number} maxCount
* @param {string} nanosValue
* @returns {Promise<object[]>}
*/
async function fetchHitsInInterval(
searchSource,
timeFieldName,
timeFieldSortDir,
startRangeMillis,
endRangeMillis,
afterTimeValue,
tieBreakerField,
tieBreakerValue,
maxCount,
nanosValue
) {
const startRange = {
[timeFieldSortDir === 'asc' ? 'gte' : 'lte']: convertTimeValueToIso(startRangeMillis, nanosValue),
};
const endRange = endRangeMillis === null ? {} : {
[timeFieldSortDir === 'asc' ? 'lte' : 'gte']: convertTimeValueToIso(endRangeMillis, nanosValue),
};
const response = await searchSource
.setField('size', maxCount)
.setField('query', {
query: {
constant_score: {
filter: {
range: {
[timeFieldName]: {
format: 'strict_date_optional_time',
...startRange,
...endRange,
}
},
},
},
},
language: 'lucene'
})
.setField('searchAfter', [
afterTimeValue,
tieBreakerValue
])
.setField('sort', [
{ [timeFieldName]: timeFieldSortDir },
{ [tieBreakerField]: timeFieldSortDir },
])
.setField('version', true)
.fetch();
return response.hits ? response.hits.hits : [];
}
}
/**
* Generate a sequence of pairs from the iterable that looks like
* `[[x_0, x_1], [x_1, x_2], [x_2, x_3], ..., [x_(n-1), x_n]]`.
*
* @param {Iterable<any>} iterable
* @returns {IterableIterator<(any[])>}
*/
function* asPairs(iterable) {
let currentPair = [];
for (const value of iterable) {
currentPair = [...currentPair, value].slice(-2);
if (currentPair.length === 2) {
yield currentPair;
}
}
}
export {
fetchContextProvider,
};

View file

@ -0,0 +1,123 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-ignore
import { SearchSourceProvider, SearchSource } from 'ui/courier';
import { IPrivate } from 'ui/private';
import { IndexPatternEnhanced, IndexPatternGetProvider } from 'ui/index_patterns/_index_pattern';
import { Filter } from '@kbn/es-query';
import { reverseSortDir, SortDirection } from './utils/sorting';
import { extractNanos, convertIsoToMillis } from './utils/date_conversion';
import { fetchHitsInInterval } from './utils/fetch_hits_in_interval';
import { generateIntervals } from './utils/generate_intervals';
import { getEsQuerySearchAfter } from './utils/get_es_query_search_after';
import { getEsQuerySort } from './utils/get_es_query_sort';
export type SurrDocType = 'successors' | 'predecessors';
export interface EsHitRecord {
fields: Record<string, any>;
sort: number[];
_source: Record<string, any>;
}
export type EsHitRecordList = EsHitRecord[];
const DAY_MILLIS = 24 * 60 * 60 * 1000;
// look from 1 day up to 10000 days into the past and future
const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map(days => days * DAY_MILLIS);
function fetchContextProvider(indexPatterns: IndexPatternGetProvider, Private: IPrivate) {
const SearchSourcePrivate: any = Private(SearchSourceProvider);
return {
fetchSurroundingDocs,
};
/**
* Fetch successor or predecessor documents of a given anchor document
*
* @param {SurrDocType} type - `successors` or `predecessors`
* @param {string} indexPatternId
* @param {EsHitRecord} anchor - anchor record
* @param {string} timeField - name of the timefield, that's sorted on
* @param {string} tieBreakerField - name of the tie breaker, the 2nd sort field
* @param {SortDirection} sortDir - direction of sorting
* @param {number} size - number of records to retrieve
* @param {Filter[]} filters - to apply in the elastic query
* @returns {Promise<object[]>}
*/
async function fetchSurroundingDocs(
type: SurrDocType,
indexPatternId: string,
anchor: EsHitRecord,
timeField: string,
tieBreakerField: string,
sortDir: SortDirection,
size: number,
filters: Filter[]
) {
const indexPattern = await indexPatterns.get(indexPatternId);
const searchSource = await createSearchSource(indexPattern, filters);
const sortDirToApply = type === 'successors' ? sortDir : reverseSortDir(sortDir);
const nanos = indexPattern.isTimeNanosBased() ? extractNanos(anchor._source[timeField]) : '';
const timeValueMillis =
nanos !== '' ? convertIsoToMillis(anchor._source[timeField]) : anchor.sort[0];
const intervals = generateIntervals(LOOKUP_OFFSETS, timeValueMillis, type, sortDir);
let documents: EsHitRecordList = [];
for (const interval of intervals) {
const remainingSize = size - documents.length;
if (remainingSize <= 0) {
break;
}
const searchAfter = getEsQuerySearchAfter(type, documents, timeField, anchor, nanos);
const sort = getEsQuerySort(timeField, tieBreakerField, sortDirToApply);
const hits = await fetchHitsInInterval(
searchSource,
timeField,
sort,
sortDirToApply,
interval,
searchAfter,
remainingSize,
nanos
);
documents =
type === 'successors' ? [...documents, ...hits] : [...hits.slice().reverse(), ...documents];
}
return documents;
}
async function createSearchSource(indexPattern: IndexPatternEnhanced, filters: Filter[]) {
return new SearchSourcePrivate()
.setParent(false)
.setField('index', indexPattern)
.setField('filter', filters);
}
}
export { fetchContextProvider };

View file

@ -16,19 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { reverseSortDir, SortDirection } from '../sorting';
import expect from '@kbn/expect';
import {
reverseSortDirection,
} from '../sorting';
describe('context app', function () {
describe('function reverseSortDirection', function () {
it('should reverse a direction given as a string', function () {
expect(reverseSortDirection('asc')).to.eql('desc');
expect(reverseSortDirection('desc')).to.eql('asc');
});
describe('function reverseSortDir', function() {
test('reverse a given sort direction', function() {
expect(reverseSortDir(SortDirection.asc)).toBe(SortDirection.desc);
expect(reverseSortDir(SortDirection.desc)).toBe(SortDirection.asc);
});
});

View file

@ -24,7 +24,7 @@ import moment from 'moment';
* 9ns -> 000000009
* 10000ns -> 0000010000
*/
export function extractNanoSeconds(timeFieldValue: string = ''): string {
export function extractNanos(timeFieldValue: string = ''): string {
const fractionSeconds = timeFieldValue.split('.')[1].replace('Z', '');
return fractionSeconds.length !== 9 ? fractionSeconds.padEnd(9, '0') : fractionSeconds;
}
@ -33,7 +33,7 @@ export function extractNanoSeconds(timeFieldValue: string = ''): string {
* extract the nanoseconds as string of a given ISO formatted timestamp
*/
export function convertIsoToNanosAsStr(isoValue: string): string {
const nanos = extractNanoSeconds(isoValue);
const nanos = extractNanos(isoValue);
const millis = convertIsoToMillis(isoValue);
return `${millis}${nanos.substr(3, 6)}`;
}

View file

@ -0,0 +1,82 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SearchSource } from 'ui/courier';
import { convertTimeValueToIso } from './date_conversion';
import { SortDirection } from './sorting';
import { EsHitRecordList } from '../context';
import { IntervalValue } from './generate_intervals';
import { EsQuerySort } from './get_es_query_sort';
import { EsQuerySearchAfter } from './get_es_query_search_after';
interface RangeQuery {
format: string;
lte?: string | null;
gte?: string | null;
}
/**
* Fetch the hits between a given `interval` up to a maximum of `maxCount` documents.
* The documents are sorted by `sort`
*
* The `searchSource` is assumed to have the appropriate index pattern
* and filters set.
*/
export async function fetchHitsInInterval(
searchSource: SearchSource,
timeField: string,
sort: EsQuerySort,
sortDir: SortDirection,
interval: IntervalValue[],
searchAfter: EsQuerySearchAfter,
maxCount: number,
nanosValue: string
): Promise<EsHitRecordList> {
const range: RangeQuery = {
format: 'strict_date_optional_time',
};
const [start, stop] = interval;
if (start) {
range[sortDir === SortDirection.asc ? 'gte' : 'lte'] = convertTimeValueToIso(start, nanosValue);
}
if (stop) {
range[sortDir === SortDirection.asc ? 'lte' : 'gte'] = convertTimeValueToIso(stop, nanosValue);
}
const response = await searchSource
.setField('size', maxCount)
.setField('query', {
query: {
constant_score: {
filter: {
range: {
[timeField]: range,
},
},
},
},
language: 'lucene',
})
.setField('searchAfter', searchAfter)
.setField('sort', sort)
.setField('version', true)
.fetch();
return response.hits ? response.hits.hits : [];
}

View file

@ -0,0 +1,54 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SortDirection } from './sorting';
export type IntervalValue = number | null;
/**
* Generate a sequence of pairs from the iterable that looks like
* `[[x_0, x_1], [x_1, x_2], [x_2, x_3], ..., [x_(n-1), x_n]]`.
*/
export function* asPairs(iterable: Iterable<IntervalValue>): IterableIterator<IntervalValue[]> {
let currentPair: IntervalValue[] = [];
for (const value of iterable) {
currentPair = [...currentPair, value].slice(-2);
if (currentPair.length === 2) {
yield currentPair;
}
}
}
/**
* Returns a iterable containing intervals `[start,end]` for Elasticsearch date range queries
* depending on type (`successors` or `predecessors`) and sort (`asc`, `desc`) these are ascending or descending intervals.
*/
export function generateIntervals(
offsets: number[],
startTime: number,
type: string,
sort: SortDirection
): IterableIterator<IntervalValue[]> {
const offsetSign =
(sort === SortDirection.asc && type === 'successors') ||
(sort === SortDirection.desc && type === 'predecessors')
? 1
: -1;
// ending with `null` opens the last interval
return asPairs([...offsets.map(offset => startTime + offset * offsetSign), null]);
}

View file

@ -0,0 +1,52 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { convertIsoToNanosAsStr } from './date_conversion';
import { SurrDocType, EsHitRecordList, EsHitRecord } from '../context';
export type EsQuerySearchAfter = [string | number, string | number];
/**
* Get the searchAfter query value for elasticsearch
* When there are already documents available, which means successors or predecessors
* were already fetched, the new searchAfter for the next fetch has to be the sort value
* of the first (prececessor), or last (successor) of the list
*/
export function getEsQuerySearchAfter(
type: SurrDocType,
documents: EsHitRecordList,
timeFieldName: string,
anchor: EsHitRecord,
nanoSeconds: string
): EsQuerySearchAfter {
if (documents.length) {
// already surrounding docs -> first or last record is used
const afterTimeRecIdx = type === 'successors' && documents.length ? documents.length - 1 : 0;
const afterTimeDoc = documents[afterTimeRecIdx];
const afterTimeValue = nanoSeconds
? convertIsoToNanosAsStr(afterTimeDoc._source[timeFieldName])
: afterTimeDoc.sort[0];
return [afterTimeValue, afterTimeDoc.sort[1]];
}
// if data_nanos adapt timestamp value for sorting, since numeric value was rounded by browser
// ES search_after also works when number is provided as string
return [
nanoSeconds ? convertIsoToNanosAsStr(anchor._source[timeFieldName]) : anchor.sort[0],
anchor.sort[1],
];
}

View file

@ -0,0 +1,38 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SortDirection } from './sorting';
type EsQuerySortValue = Record<string, SortDirection>;
export type EsQuerySort = [EsQuerySortValue, EsQuerySortValue];
/**
* Returns `EsQuerySort` which is used to sort records in the ES query
* https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html
* @param timeField
* @param tieBreakerField
* @param sortDir
*/
export function getEsQuerySort(
timeField: string,
tieBreakerField: string,
sortDir: SortDirection
): EsQuerySort {
return [{ [timeField]: sortDir }, { [tieBreakerField]: sortDir }];
}

View file

@ -17,52 +17,36 @@
* under the License.
*/
import { IndexPattern } from 'src/legacy/core_plugins/data/public';
export enum SortDirection {
asc = 'asc',
desc = 'desc',
}
/**
* 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'];
const META_FIELD_NAMES: string[] = ['_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
));
export function getFirstSortableField(indexPattern: IndexPattern, fieldNames: string[]) {
const sortableFields = fieldNames.filter(
fieldName =>
META_FIELD_NAMES.includes(fieldName) ||
// @ts-ignore
(indexPattern.fields.byName[fieldName] || { sortable: false }).sortable
);
return sortableFields[0];
}
/**
* A sort order string.
*
* @typedef {('asc'|'desc')} SortDirection
*/
/**
* Return the reversed sort direction.
*
* @param {(SortDirection)} sortDirection
*
* @returns {(SortDirection)}
*/
function reverseSortDirection(sortDirection) {
return (sortDirection === 'asc' ? 'desc' : 'asc');
export function reverseSortDir(sortDirection: SortDirection) {
return sortDirection === SortDirection.asc ? SortDirection.desc : SortDirection.asc;
}
export {
getFirstSortableField,
reverseSortDirection,
};

View file

@ -108,17 +108,16 @@ export function QueryActionsProvider(Private, Promise) {
}
setLoadingStatus(state)(type);
const [sortField, sortDir] = sort;
return Promise.try(() => (
fetchSurroundingDocs(
type,
indexPatternId,
sort[0],
sort[1],
anchor.fields[sort[0]][0],
anchor.sort[0],
anchor,
sortField,
tieBreakerField,
anchor.sort[1],
sortDir,
count,
filters
)

View file

@ -29,6 +29,16 @@ export interface IndexPattern {
title: string;
timeFieldName?: string;
}
// 'enhanced' IndexPattern returned by IndexPatternProvider
// reason for having this seperate interface are several tests
// that currently depend on an interface without methods to succeed
export interface IndexPatternEnhanced extends IndexPattern {
isTimeNanosBased: () => boolean;
}
export interface IndexPatternGetProvider {
get: (id: string) => IndexPatternEnhanced;
}
export interface StaticIndexPatternField {
name: string;