diff --git a/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap b/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap new file mode 100644 index 00000000000000..972df33dfa37dd --- /dev/null +++ b/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap @@ -0,0 +1,274 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`context predecessors function fetchPredecessors should perform multiple queries until the expected hit count is returned 1`] = ` +Array [ + Array [ + "index", + Object { + "fields": Object { + "getByName": [MockFunction] { + "calls": Array [ + Array [ + "@timestamp", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + }, + "id": "DATA_VIEW_ID", + "isTimeNanosBased": [Function], + "popularizeField": [Function], + "timeFieldName": "@timestamp", + }, + ], + Array [ + "filter", + Array [], + ], + Array [ + "trackTotalHits", + false, + ], + Array [ + "size", + 3, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1972-09-27T00:00:00.000Z", + "lte": "1972-09-28T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "test", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1972-09-27T00:00:00.000Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "asc", + }, + }, + Object { + "_doc": "asc", + }, + ], + ], + Array [ + "version", + true, + ], + Array [ + "size", + 2, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1972-09-28T00:00:00.000Z", + "lte": "1972-10-04T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "test", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1972-09-27T00:00:00.000Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "asc", + }, + }, + Object { + "_doc": "asc", + }, + ], + ], + Array [ + "version", + true, + ], + Array [ + "size", + 2, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1972-10-04T00:00:00.000Z", + "lte": "1972-10-27T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "test", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1972-09-27T00:00:00.000Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "asc", + }, + }, + Object { + "_doc": "asc", + }, + ], + ], + Array [ + "version", + true, + ], + Array [ + "size", + 2, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1972-10-27T00:00:00.000Z", + "lte": "1973-09-27T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "test", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1972-09-27T00:00:00.000Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "asc", + }, + }, + Object { + "_doc": "asc", + }, + ], + ], + Array [ + "version", + true, + ], +] +`; diff --git a/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap b/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap new file mode 100644 index 00000000000000..1f8623595b707d --- /dev/null +++ b/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap @@ -0,0 +1,274 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`context successors function fetchSuccessors should perform multiple queries until the expected hit count is returned 1`] = ` +Array [ + Array [ + "index", + Object { + "fields": Object { + "getByName": [MockFunction] { + "calls": Array [ + Array [ + "@timestamp", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + }, + "id": "DATA_VIEW_ID", + "isTimeNanosBased": [Function], + "popularizeField": [Function], + "timeFieldName": "@timestamp", + }, + ], + Array [ + "filter", + Array [], + ], + Array [ + "trackTotalHits", + false, + ], + Array [ + "size", + 4, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1978-03-19T00:00:00.000Z", + "lte": "1978-03-20T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "1", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1978-03-20T00:00:00.000Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "desc", + }, + }, + Object { + "_doc": "desc", + }, + ], + ], + Array [ + "version", + true, + ], + Array [ + "size", + 1, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1978-03-13T00:00:00.000Z", + "lte": "1978-03-19T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "1", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1978-03-19T23:59:59.998Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "desc", + }, + }, + Object { + "_doc": "desc", + }, + ], + ], + Array [ + "version", + true, + ], + Array [ + "size", + 1, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1978-02-18T00:00:00.000Z", + "lte": "1978-03-13T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "1", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1978-03-19T23:59:59.998Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "desc", + }, + }, + Object { + "_doc": "desc", + }, + ], + ], + Array [ + "version", + true, + ], + Array [ + "size", + 1, + ], + Array [ + "query", + Object { + "language": "lucene", + "query": Object { + "bool": Object { + "must": Object { + "constant_score": Object { + "filter": Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "1977-03-20T00:00:00.000Z", + "lte": "1978-02-18T00:00:00.000Z", + }, + }, + }, + }, + }, + "must_not": Object { + "ids": Object { + "values": Array [ + "1", + ], + }, + }, + }, + }, + }, + ], + Array [ + "searchAfter", + Array [ + "1978-03-19T23:59:59.998Z", + 0, + ], + ], + Array [ + "sort", + Array [ + Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "order": "desc", + }, + }, + Object { + "_doc": "desc", + }, + ], + ], + Array [ + "version", + true, + ], +] +`; diff --git a/src/plugins/discover/public/application/context/services/_stubs.ts b/src/plugins/discover/public/application/context/services/_stubs.ts index 0122d325fd98de..8df5710f720202 100644 --- a/src/plugins/discover/public/application/context/services/_stubs.ts +++ b/src/plugins/discover/public/application/context/services/_stubs.ts @@ -44,10 +44,13 @@ export function createSearchSourceStub(hits: EsHitRecord[], timeField?: string) const searchSourceStub: any = { _stubHits: hits, _stubTimeField: timeField, - _createStubHit: (timestamp: number, tiebreaker = 0) => ({ - [searchSourceStub._stubTimeField]: timestamp, - sort: [timestamp, tiebreaker], - }), + _createStubHit: (timestamp: number, tiebreaker = 0) => { + const value = new Date(timestamp).toISOString(); + return { + [searchSourceStub._stubTimeField]: value, + sort: [value, tiebreaker], + }; + }, setParent: sinon.spy(() => searchSourceStub), setField: sinon.spy(() => searchSourceStub), removeField: sinon.spy(() => searchSourceStub), @@ -74,13 +77,13 @@ export function createContextSearchSourceStub(timeFieldName: string) { const sortDirection = lastSort[0][timeField].order; const sortFunction = sortDirection === 'asc' - ? (first: SortHit, second: SortHit) => first[timeField] - second[timeField] - : (first: SortHit, second: SortHit) => second[timeField] - first[timeField]; + ? (first: SortHit, second: SortHit) => (first[timeField] < second[timeField] ? -1 : 1) + : (first: SortHit, second: SortHit) => (second[timeField] < first[timeField] ? -1 : 1); const filteredHits = searchSourceStub._stubHits .filter( (hit: SortHit) => - moment(hit[timeField]).isSameOrAfter(timeRange.gte) && - moment(hit[timeField]).isSameOrBefore(timeRange.lte) + moment(hit[timeField]).isSameOrAfter(moment(timeRange.gte)) && + moment(hit[timeField]).isSameOrBefore(moment(timeRange.lte)) ) .sort(sortFunction); diff --git a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts index 6ade7624c2bf98..8e96d3807e7b5b 100644 --- a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts @@ -34,7 +34,6 @@ describe('context predecessors', function () { let dataPluginMock: DataPublicPluginStart; let fetchPredecessors: ( timeValIso: string, - timeValNr: number, tieBreakerField: string, tieBreakerValue: number, size: number @@ -42,18 +41,20 @@ describe('context predecessors', function () { // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockSearchSource: any; - const dataView = { - id: 'DATA_VIEW_ID', - timeFieldName: '@timestamp', - isTimeNanosBased: () => false, - popularizeField: () => {}, - fields: { - getByName: jest.fn(), - }, - } as unknown as DataView; + let dataView: DataView; describe('function fetchPredecessors', function () { beforeEach(() => { + dataView = { + id: 'DATA_VIEW_ID', + timeFieldName: '@timestamp', + isTimeNanosBased: () => false, + popularizeField: () => {}, + fields: { + getByName: jest.fn(), + }, + } as unknown as DataView; + mockSearchSource = createContextSearchSourceStub('@timestamp'); dataPluginMock = { search: { @@ -63,14 +64,14 @@ describe('context predecessors', function () { }, } as unknown as DataPublicPluginStart; - fetchPredecessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size = 10) => { + fetchPredecessors = (timeValIso, tieBreakerField, tieBreakerValue, size = 10) => { const anchor = buildDataTableRecord( { _id: 'test', _source: { [dataView.timeFieldName!]: timeValIso, }, - sort: [timeValNr, tieBreakerValue], + sort: [timeValIso, tieBreakerValue], } as EsHitRecord, dataView, true @@ -100,17 +101,15 @@ describe('context predecessors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; - return fetchPredecessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 3).then( - ({ rows }) => { - expect(mockSearchSource.fetch$.calledOnce).toBe(true); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(0, 3), - dataView, - }) - ); - } - ); + return fetchPredecessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 3).then(({ rows }) => { + expect(mockSearchSource.fetch$.calledOnce).toBe(true); + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 3), + dataView, + }) + ); + }); }); it('should perform multiple queries with the last being unrestricted when too few hits are returned', function () { @@ -122,30 +121,28 @@ describe('context predecessors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 2990), ]; - return fetchPredecessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 6).then( - ({ rows }) => { - const intervals: Timestamp[] = mockSearchSource.setField.args - .filter(([property]: string) => property === 'query') - .map(([, { query }]: [string, { query: Query }]) => - get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']) - ); - - expect( - intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) - ).toBe(true); - // should have started at the given time - expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); - // should have ended with a half-open interval - expect(Object.keys(last(intervals) ?? {})).toEqual(['format', 'gte']); - expect(intervals.length).toBeGreaterThan(1); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(0, 3), - dataView, - }) + return fetchPredecessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 6).then(({ rows }) => { + const intervals: Timestamp[] = mockSearchSource.setField.args + .filter(([property]: string) => property === 'query') + .map(([, { query }]: [string, { query: Query }]) => + get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']) ); - } - ); + + expect( + intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) + ).toBe(true); + // should have started at the given time + expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); + // should have ended with a half-open interval + expect(Object.keys(last(intervals) ?? {})).toEqual(['format', 'gte']); + expect(intervals.length).toBeGreaterThan(1); + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 3), + dataView, + }) + ); + }); }); it('should perform multiple queries until the expected hit count is returned', function () { @@ -156,47 +153,38 @@ describe('context predecessors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; - return fetchPredecessors(ANCHOR_TIMESTAMP_1000, MS_PER_DAY * 1000, '_doc', 0, 3).then( - ({ rows }) => { - const intervals: Timestamp[] = mockSearchSource.setField.args - .filter(([property]: string) => property === 'query') - .map(([, { query }]: [string, { query: Query }]) => { - return get(query, [ - 'bool', - 'must', - 'constant_score', - 'filter', - 'range', - '@timestamp', - ]); - }); + return fetchPredecessors(ANCHOR_TIMESTAMP_1000, '_doc', 0, 3).then(({ rows }) => { + expect(mockSearchSource.setField.args).toMatchSnapshot(); - // should have started at the given time - expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 1000).toISOString()); - // should have stopped before reaching MS_PER_DAY * 1700 - expect(moment(last(intervals)?.lte).valueOf()).toBeLessThan(MS_PER_DAY * 1700); - expect(intervals.length).toBeGreaterThan(1); + const intervals: Timestamp[] = mockSearchSource.setField.args + .filter(([property]: string) => property === 'query') + .map(([, { query }]: [string, { query: Query }]) => { + return get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']); + }); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(-3), - dataView, - }) - ); - } - ); + // should have started at the given time + expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 1000).toISOString()); + // should have stopped before reaching MS_PER_DAY * 1700 + expect(moment(last(intervals)?.lte).valueOf()).toBeLessThan(MS_PER_DAY * 1700); + expect(intervals.length).toBeGreaterThan(1); + + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) + ); + }); }); it('should return an empty array when no hits were found', function () { - return fetchPredecessors(ANCHOR_TIMESTAMP_3, MS_PER_DAY * 3, '_doc', 0, 3).then( - ({ rows }) => { - expect(rows).toEqual([]); - } - ); + return fetchPredecessors(ANCHOR_TIMESTAMP_3, '_doc', 0, 3).then(({ rows }) => { + expect(rows).toEqual([]); + }); }); it('should configure the SearchSource to not inherit from the implicit root', function () { - return fetchPredecessors(ANCHOR_TIMESTAMP_3, MS_PER_DAY * 3, '_doc', 0, 3).then(() => { + return fetchPredecessors(ANCHOR_TIMESTAMP_3, '_doc', 0, 3).then(() => { const setParentSpy = mockSearchSource.setParent; expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); expect(setParentSpy.called).toBe(true); @@ -204,7 +192,7 @@ describe('context predecessors', function () { }); it('should set the tiebreaker sort order to the opposite as the time field', function () { - return fetchPredecessors(ANCHOR_TIMESTAMP, MS_PER_DAY, '_doc', 0, 3).then(() => { + return fetchPredecessors(ANCHOR_TIMESTAMP, '_doc', 0, 3).then(() => { expect( mockSearchSource.setField.calledWith('sort', [ { '@timestamp': { order: 'asc', format: 'strict_date_optional_time' } }, @@ -227,14 +215,14 @@ describe('context predecessors', function () { }, } as unknown as DataPublicPluginStart; - fetchPredecessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size = 10) => { + fetchPredecessors = (timeValIso, tieBreakerField, tieBreakerValue, size = 10) => { const anchor = buildDataTableRecord( { _id: 'test', _source: { [dataView.timeFieldName!]: timeValIso, }, - sort: [timeValNr, tieBreakerValue], + sort: [timeValIso, tieBreakerValue], } as EsHitRecord, dataView, true @@ -264,21 +252,19 @@ describe('context predecessors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; - return fetchPredecessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 3).then( - ({ rows }) => { - const setFieldsSpy = mockSearchSource.setField.withArgs('fields'); - const removeFieldsSpy = mockSearchSource.removeField.withArgs('fieldsFromSource'); - expect(mockSearchSource.fetch$.calledOnce).toBe(true); - expect(removeFieldsSpy.calledOnce).toBe(true); - expect(setFieldsSpy.calledOnce).toBe(true); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(0, 3), - dataView, - }) - ); - } - ); + return fetchPredecessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 3).then(({ rows }) => { + const setFieldsSpy = mockSearchSource.setField.withArgs('fields'); + const removeFieldsSpy = mockSearchSource.removeField.withArgs('fieldsFromSource'); + expect(mockSearchSource.fetch$.calledOnce).toBe(true); + expect(removeFieldsSpy.calledOnce).toBe(true); + expect(setFieldsSpy.calledOnce).toBe(true); + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 3), + dataView, + }) + ); + }); }); }); }); diff --git a/src/plugins/discover/public/application/context/services/context.successors.test.ts b/src/plugins/discover/public/application/context/services/context.successors.test.ts index 39cc05bd765713..fb480c0b4e8738 100644 --- a/src/plugins/discover/public/application/context/services/context.successors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.successors.test.ts @@ -32,7 +32,6 @@ interface Timestamp { describe('context successors', function () { let fetchSuccessors: ( timeValIso: string, - timeValNr: number, tieBreakerField: string, tieBreakerValue: number, size: number @@ -40,18 +39,20 @@ describe('context successors', function () { let dataPluginMock: DataPublicPluginStart; // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockSearchSource: any; - const dataView = { - id: 'DATA_VIEW_ID', - timeFieldName: '@timestamp', - isTimeNanosBased: () => false, - popularizeField: () => {}, - fields: { - getByName: jest.fn(), - }, - } as unknown as DataView; + let dataView: DataView; describe('function fetchSuccessors', function () { beforeEach(() => { + dataView = { + id: 'DATA_VIEW_ID', + timeFieldName: '@timestamp', + isTimeNanosBased: () => false, + popularizeField: () => {}, + fields: { + getByName: jest.fn(), + }, + } as unknown as DataView; + mockSearchSource = createContextSearchSourceStub('@timestamp'); dataPluginMock = { @@ -62,7 +63,7 @@ describe('context successors', function () { }, } as unknown as DataPublicPluginStart; - fetchSuccessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { + fetchSuccessors = (timeValIso, tieBreakerField, tieBreakerValue, size) => { const anchor = buildDataTableRecord( { _index: 't', @@ -70,7 +71,7 @@ describe('context successors', function () { _source: { [dataView.timeFieldName!]: timeValIso, }, - sort: [timeValNr, tieBreakerValue], + sort: [timeValIso, tieBreakerValue], }, dataView, true @@ -100,17 +101,15 @@ describe('context successors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 2), ]; - return fetchSuccessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 3).then( - ({ rows }) => { - expect(mockSearchSource.fetch$.calledOnce).toBe(true); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(-3), - dataView, - }) - ); - } - ); + return fetchSuccessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 3).then(({ rows }) => { + expect(mockSearchSource.fetch$.calledOnce).toBe(true); + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) + ); + }); }); it('should perform multiple queries with the last being unrestricted when too few hits are returned', function () { @@ -122,30 +121,28 @@ describe('context successors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 2990), ]; - return fetchSuccessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 6).then( - ({ rows }) => { - const intervals: Timestamp[] = mockSearchSource.setField.args - .filter(([property]: [string]) => property === 'query') - .map(([, { query }]: [string, { query: Query }]) => - get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']) - ); - - expect( - intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) - ).toBe(true); - // should have started at the given time - expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); - // should have ended with a half-open interval - expect(Object.keys(last(intervals) ?? {})).toEqual(['format', 'lte']); - expect(intervals.length).toBeGreaterThan(1); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(-3), - dataView, - }) + return fetchSuccessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 6).then(({ rows }) => { + const intervals: Timestamp[] = mockSearchSource.setField.args + .filter(([property]: [string]) => property === 'query') + .map(([, { query }]: [string, { query: Query }]) => + get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']) ); - } - ); + + expect( + intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) + ).toBe(true); + // should have started at the given time + expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); + // should have ended with a half-open interval + expect(Object.keys(last(intervals) ?? {})).toEqual(['format', 'lte']); + expect(intervals.length).toBeGreaterThan(1); + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) + ); + }); }); it('should perform multiple queries until the expected hit count is returned', function () { @@ -158,37 +155,37 @@ describe('context successors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; - return fetchSuccessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 4).then( - ({ rows }) => { - const intervals: Timestamp[] = mockSearchSource.setField.args - .filter(([property]: [string]) => property === 'query') - .map(([, { query }]: [string, { query: Query }]) => - get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']) - ); - - // should have started at the given time - expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); - // should have stopped before reaching MS_PER_DAY * 2200 - expect(moment(last(intervals)?.gte).valueOf()).toBeGreaterThan(MS_PER_DAY * 2200); - expect(intervals.length).toBeGreaterThan(1); - expect(rows).toEqual( - buildDataTableRecordList({ - records: mockSearchSource._stubHits.slice(0, 4), - dataView, - }) + return fetchSuccessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 4).then(({ rows }) => { + expect(mockSearchSource.setField.args).toMatchSnapshot(); + + const intervals: Timestamp[] = mockSearchSource.setField.args + .filter(([property]: [string]) => property === 'query') + .map(([, { query }]: [string, { query: Query }]) => + get(query, ['bool', 'must', 'constant_score', 'filter', 'range', '@timestamp']) ); - } - ); + + // should have started at the given time + expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); + // should have stopped before reaching MS_PER_DAY * 2200 + expect(moment(last(intervals)?.gte).valueOf()).toBeGreaterThan(MS_PER_DAY * 2200); + expect(intervals.length).toBeGreaterThan(1); + expect(rows).toEqual( + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 4), + dataView, + }) + ); + }); }); it('should return an empty array when no hits were found', function () { - return fetchSuccessors(ANCHOR_TIMESTAMP_3, MS_PER_DAY * 3, '_doc', 0, 3).then(({ rows }) => { + return fetchSuccessors(ANCHOR_TIMESTAMP_3, '_doc', 0, 3).then(({ rows }) => { expect(rows).toEqual([]); }); }); it('should configure the SearchSource to not inherit from the implicit root', function () { - return fetchSuccessors(ANCHOR_TIMESTAMP_3, MS_PER_DAY * 3, '_doc', 0, 3).then(() => { + return fetchSuccessors(ANCHOR_TIMESTAMP_3, '_doc', 0, 3).then(() => { const setParentSpy = mockSearchSource.setParent; expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); expect(setParentSpy.called).toBe(true); @@ -196,7 +193,7 @@ describe('context successors', function () { }); it('should set the tiebreaker sort order to the same as the time field', function () { - return fetchSuccessors(ANCHOR_TIMESTAMP, MS_PER_DAY, '_doc', 0, 3).then(() => { + return fetchSuccessors(ANCHOR_TIMESTAMP, '_doc', 0, 3).then(() => { expect( mockSearchSource.setField.calledWith('sort', [ { '@timestamp': { order: SortDirection.desc, format: 'strict_date_optional_time' } }, @@ -219,7 +216,7 @@ describe('context successors', function () { }, } as unknown as DataPublicPluginStart; - fetchSuccessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { + fetchSuccessors = (timeValIso, tieBreakerField, tieBreakerValue, size) => { const anchor = buildDataTableRecord( { _id: '1', @@ -227,7 +224,7 @@ describe('context successors', function () { _source: { [dataView.timeFieldName!]: timeValIso, }, - sort: [timeValNr, tieBreakerValue], + sort: [timeValIso, tieBreakerValue], }, dataView, true @@ -257,7 +254,7 @@ describe('context successors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 2), ]; - return fetchSuccessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 3).then( + return fetchSuccessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 3).then( ({ rows, interceptedWarnings }) => { expect(mockSearchSource.fetch$.calledOnce).toBe(true); expect(rows).toEqual( @@ -291,7 +288,7 @@ describe('context successors', function () { }, } as unknown as DataPublicPluginStart; - fetchSuccessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { + fetchSuccessors = (timeValIso, tieBreakerField, tieBreakerValue, size) => { const anchor = buildDataTableRecord( { _id: '1', @@ -299,7 +296,7 @@ describe('context successors', function () { _source: { [dataView.timeFieldName!]: timeValIso, }, - sort: [timeValNr, tieBreakerValue], + sort: [timeValIso, tieBreakerValue], }, dataView, true @@ -332,7 +329,7 @@ describe('context successors', function () { mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 2), ]; - return fetchSuccessors(ANCHOR_TIMESTAMP_3000, MS_PER_DAY * 3000, '_doc', 0, 3).then( + return fetchSuccessors(ANCHOR_TIMESTAMP_3000, '_doc', 0, 3).then( ({ rows, interceptedWarnings }) => { expect(mockSearchSource.fetch$.calledOnce).toBe(true); expect(rows).toEqual( diff --git a/src/plugins/discover/public/application/context/services/context.ts b/src/plugins/discover/public/application/context/services/context.ts index a4df96fe0bdbb5..b375e3cce75f63 100644 --- a/src/plugins/discover/public/application/context/services/context.ts +++ b/src/plugins/discover/public/application/context/services/context.ts @@ -71,10 +71,11 @@ export async function fetchSurroundingDocs( const anchorRaw = anchor.raw!; const nanos = dataView.isTimeNanosBased() ? extractNanos(anchorRaw.fields?.[timeField][0]) : ''; - const timeValueMillis = - nanos !== '' ? convertIsoToMillis(anchorRaw.fields?.[timeField][0]) : anchorRaw.sort?.[0]; + const timeValueMillis = convertIsoToMillis( + nanos !== '' ? anchorRaw.fields?.[timeField][0] : anchorRaw.sort?.[0] + ); - const intervals = generateIntervals(LOOKUP_OFFSETS, timeValueMillis as number, type, sortDir); + const intervals = generateIntervals(LOOKUP_OFFSETS, timeValueMillis, type, sortDir); let rows: DataTableRecord[] = []; let interceptedWarnings: SearchResponseWarning[] = [];