From e63838dc6d0a27098ddcfd2ba776742206389531 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 10 Dec 2015 13:52:29 -0700 Subject: [PATCH 1/2] [indexPattern] added #toDetailedIndexList() --- .../__tests__/_index_pattern.js | 67 +++----- .../__tests__/calculate_indices.js | 59 +++++-- .../index_patterns/__tests__/intervals.js | 151 +++++++++++++++--- .../index_patterns/_calculate_indices.js | 25 ++- .../public/index_patterns/_index_pattern.js | 35 ++-- src/ui/public/index_patterns/_intervals.js | 16 +- 6 files changed, 244 insertions(+), 109 deletions(-) diff --git a/src/ui/public/index_patterns/__tests__/_index_pattern.js b/src/ui/public/index_patterns/__tests__/_index_pattern.js index d3aad1ec16ec6..36975bdbc319b 100644 --- a/src/ui/public/index_patterns/__tests__/_index_pattern.js +++ b/src/ui/public/index_patterns/__tests__/_index_pattern.js @@ -44,13 +44,19 @@ describe('index pattern', function () { // stub calculateIndices calculateIndices = sinon.spy(function () { - return $injector.get('Promise').resolve(['foo', 'bar']); + return $injector.get('Promise').resolve([ + { index: 'foo', max: Infinity, min: -Infinity }, + { index: 'bar', max: Infinity, min: -Infinity } + ]); }); Private.stub(require('ui/index_patterns/_calculate_indices'), calculateIndices); // spy on intervals intervals = Private(require('ui/index_patterns/_intervals')); - sinon.stub(intervals, 'toIndexList').returns(['foo', 'bar']); + sinon.stub(intervals, 'toIndexList').returns([ + { index: 'foo', max: Infinity, min: -Infinity }, + { index: 'bar', max: Infinity, min: -Infinity } + ]); IndexPattern = Private(require('ui/index_patterns/_index_pattern')); })); @@ -290,78 +296,53 @@ describe('index pattern', function () { describe('#toIndexList', function () { context('when index pattern is an interval', function () { + require('testUtils/noDigestPromises').activateForSuite(); + var interval; beforeEach(function () { interval = 'result:getInterval'; sinon.stub(indexPattern, 'getInterval').returns(interval); }); - it('invokes interval toIndexList with given start/stop times', function () { - indexPattern.toIndexList(1, 2); - $rootScope.$apply(); - + it('invokes interval toIndexList with given start/stop times', async function () { + await indexPattern.toIndexList(1, 2); var id = indexPattern.id; expect(intervals.toIndexList.calledWith(id, interval, 1, 2)).to.be(true); }); - it('is fulfilled by the result of interval toIndexList', function () { - var indexList; - indexPattern.toIndexList().then(function (val) { - indexList = val; - }); - $rootScope.$apply(); - + it('is fulfilled by the result of interval toIndexList', async function () { + var indexList = await indexPattern.toIndexList(); expect(indexList[0]).to.equal('foo'); expect(indexList[1]).to.equal('bar'); }); context('with sort order', function () { - require('testUtils/noDigestPromises').activateForSuite(); - - context('undefined', function () { - it('provides the index list in tact', async function () { - const indexList = await indexPattern.toIndexList(); - expect(indexList).to.eql(['foo', 'bar']); - }); - }); - - context('asc', function () { - it('provides the index list in tact', async function () { - const indexList = await indexPattern.toIndexList(1, 2, 'asc'); - expect(indexList).to.eql(['foo', 'bar']); - }); - }); - - context('desc', function () { - it('reverses the index list', async function () { - const indexList = await indexPattern.toIndexList(1, 2, 'desc'); - expect(indexList).to.eql(['bar', 'foo']); + it('passes the sort order to the intervals module', function () { + return indexPattern.toIndexList(1, 2, 'SORT_DIRECTION') + .then(function () { + expect(intervals.toIndexList.callCount).to.be(1); + expect(intervals.toIndexList.getCall(0).args[4]).to.be('SORT_DIRECTION'); }); }); }); }); context('when index pattern is a time-base wildcard', function () { + require('testUtils/noDigestPromises').activateForSuite(); beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); sinon.stub(indexPattern, 'hasTimeField').returns(true); sinon.stub(indexPattern, 'isWildcard').returns(true); }); - it('invokes calculateIndices with given start/stop times and sortOrder', function () { - indexPattern.toIndexList(1, 2, 'sortOrder'); - $rootScope.$apply(); - + it('invokes calculateIndices with given start/stop times and sortOrder', async function () { + await indexPattern.toIndexList(1, 2, 'sortOrder'); var id = indexPattern.id; var field = indexPattern.timeFieldName; expect(calculateIndices.calledWith(id, field, 1, 2, 'sortOrder')).to.be(true); }); - it('is fulfilled by the result of calculateIndices', function () { - var indexList; - indexPattern.toIndexList().then(function (val) { - indexList = val; - }); - $rootScope.$apply(); + it('is fulfilled by the result of calculateIndices', async function () { + var indexList = await indexPattern.toIndexList(); expect(indexList[0]).to.equal('foo'); expect(indexList[1]).to.equal('bar'); }); diff --git a/src/ui/public/index_patterns/__tests__/calculate_indices.js b/src/ui/public/index_patterns/__tests__/calculate_indices.js index d5c44ec2d00cb..286e5fc022618 100644 --- a/src/ui/public/index_patterns/__tests__/calculate_indices.js +++ b/src/ui/public/index_patterns/__tests__/calculate_indices.js @@ -1,5 +1,6 @@ describe('ui/index_patterns/_calculate_indices', () => { const _ = require('lodash'); + const pluck = require('lodash').pluck; const sinon = require('auto-release-sinon'); const expect = require('expect.js'); const ngMock = require('ngMock'); @@ -40,7 +41,7 @@ describe('ui/index_patterns/_calculate_indices', () => { function run({ start = undefined, stop = undefined } = {}) { calculateIndices('wat-*-no', '@something', start, stop).then(value => { - indices = value; + indices = pluck(value, 'index'); }); $rootScope.$apply(); config = _.first(es.fieldStats.lastCall.args); @@ -130,29 +131,45 @@ describe('ui/index_patterns/_calculate_indices', () => { }; return calculateIndices('*', 'time', null, null).then(function (resp) { - expect(resp).to.eql(['c', 'a', 'b']); + expect(pluck(resp, 'index')).to.eql(['c', 'a', 'b']); }); }); }); - context('when sorting desc', function () { - it('returns the indices in max_value order', function () { + context('when sorting asc', function () { + it('resolves to an array of objects, each with index, start, and end properties', function () { response = { indices: { - c: { fields: { time: { max_value: 10 } } }, - a: { fields: { time: { max_value: 15 } } }, - b: { fields: { time: { max_value: 1 } } }, + c: { fields: { time: { max_value: 10, min_value: 9 } } }, + a: { fields: { time: { max_value: 15, min_value: 5 } } }, + b: { fields: { time: { max_value: 1, min_value: 0 } } }, } }; - return calculateIndices('*', 'time', null, null, 'desc').then(function (resp) { - expect(resp).to.eql(['a', 'c', 'b']); + return calculateIndices('*', 'time', null, null, 'asc').then(function (resp) { + expect(resp).to.eql([ + { + index: 'b', + min: 0, + max: 1 + }, + { + index: 'a', + min: 5, + max: 15 + }, + { + index: 'c', + min: 9, + max: 10 + } + ]); }); }); }); - context('when sorting asc', function () { - it('returns the indices in min_value order', function () { + context('when sorting desc', function () { + it('resolves to an array of objects, each with index, min, and max properties', function () { response = { indices: { c: { fields: { time: { max_value: 10, min_value: 9 } } }, @@ -161,8 +178,24 @@ describe('ui/index_patterns/_calculate_indices', () => { } }; - return calculateIndices('*', 'time', null, null, 'asc').then(function (resp) { - expect(resp).to.eql(['b', 'a', 'c']); + return calculateIndices('*', 'time', null, null, 'desc').then(function (resp) { + expect(resp).to.eql([ + { + index: 'a', + max: 15, + min: 5 + }, + { + index: 'c', + max: 10, + min: 9 + }, + { + index: 'b', + max: 1, + min: 0 + }, + ]); }); }); }); diff --git a/src/ui/public/index_patterns/__tests__/intervals.js b/src/ui/public/index_patterns/__tests__/intervals.js index 58fb51a6eb7ce..1f380416d9963 100644 --- a/src/ui/public/index_patterns/__tests__/intervals.js +++ b/src/ui/public/index_patterns/__tests__/intervals.js @@ -1,5 +1,6 @@ - +var pluck = require('lodash').pluck; var moment = require('moment'); + describe('Index Patterns', function () { describe('interval.toIndexList()', function () { var expect = require('expect.js'); @@ -16,8 +17,18 @@ describe('Index Patterns', function () { var end = moment.utc('2014-01-01T08:30:00Z'); var interval = { name: 'hours', startOf: 'hour', display: 'Hourly' }; var list = intervals.toIndexList('[logstash-]YYYY.MM.DD.HH', interval, start, end); - expect(list).to.contain('logstash-2014.01.01.07'); - expect(list).to.contain('logstash-2014.01.01.08'); + expect(list).to.eql([ + { + index: 'logstash-2014.01.01.07', + min: moment.utc('2014-01-01T07:00:00').valueOf(), + max: moment.utc('2014-01-01T07:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.01.01.08', + min: moment.utc('2014-01-01T08:00:00').valueOf(), + max: moment.utc('2014-01-01T08:59:59.999').valueOf(), + } + ]); }); it('should return correct indices for daily [logstash-]YYYY.MM.DD', function () { @@ -25,23 +36,48 @@ describe('Index Patterns', function () { var end = moment(1418849261281); var interval = { name: 'days', startOf: 'day', display: 'Daily' }; var list = intervals.toIndexList('[logstash-]YYYY.MM.DD', interval, start, end); - expect(list).to.contain('logstash-2014.12.10'); - expect(list).to.contain('logstash-2014.12.11'); - expect(list).to.contain('logstash-2014.12.12'); - expect(list).to.contain('logstash-2014.12.13'); - expect(list).to.contain('logstash-2014.12.14'); - expect(list).to.contain('logstash-2014.12.15'); - expect(list).to.contain('logstash-2014.12.16'); - expect(list).to.contain('logstash-2014.12.17'); - }); - - it('should return correct indices for weekly [logstash-]GGGG.WW', function () { - var start = moment.utc(1418244231248); - var end = moment.utc(1418849261281); - var interval = { name: 'weeks', startOf: 'isoWeek', display: 'Weekly' }; - var list = intervals.toIndexList('[logstash-]GGGG.WW', interval, start, end); - expect(list).to.contain('logstash-2014.50'); - expect(list).to.contain('logstash-2014.51'); + expect(list).to.eql([ + { + index: 'logstash-2014.12.10', + min: moment.utc('2014-12-10T00:00:00').valueOf(), + max: moment.utc('2014-12-10T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.11', + min: moment.utc('2014-12-11T00:00:00').valueOf(), + max: moment.utc('2014-12-11T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.12', + min: moment.utc('2014-12-12T00:00:00').valueOf(), + max: moment.utc('2014-12-12T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.13', + min: moment.utc('2014-12-13T00:00:00').valueOf(), + max: moment.utc('2014-12-13T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.14', + min: moment.utc('2014-12-14T00:00:00').valueOf(), + max: moment.utc('2014-12-14T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.15', + min: moment.utc('2014-12-15T00:00:00').valueOf(), + max: moment.utc('2014-12-15T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.16', + min: moment.utc('2014-12-16T00:00:00').valueOf(), + max: moment.utc('2014-12-16T23:59:59.999').valueOf(), + }, + { + index: 'logstash-2014.12.17', + min: moment.utc('2014-12-17T00:00:00').valueOf(), + max: moment.utc('2014-12-17T23:59:59.999').valueOf(), + }, + ]); }); it('should return correct indices for monthly [logstash-]YYYY.MM', function () { @@ -49,9 +85,23 @@ describe('Index Patterns', function () { var end = moment.utc('2015-02-01'); var interval = { name: 'months', startOf: 'month', display: 'Monthly' }; var list = intervals.toIndexList('[logstash-]YYYY.MM', interval, start, end); - expect(list).to.contain('logstash-2014.12'); - expect(list).to.contain('logstash-2015.01'); - expect(list).to.contain('logstash-2015.02'); + expect(list).to.eql([ + { + index: 'logstash-2014.12', + min: moment.utc(0).year(2014).month(11).valueOf(), + max: moment.utc(0).year(2015).month(0).subtract(1, 'ms').valueOf(), + }, + { + index: 'logstash-2015.01', + min: moment.utc(0).year(2015).month(0).valueOf(), + max: moment.utc(0).year(2015).month(1).subtract(1, 'ms').valueOf(), + }, + { + index: 'logstash-2015.02', + min: moment.utc(0).year(2015).month(1).valueOf(), + max: moment.utc(0).year(2015).month(2).subtract(1, 'ms').valueOf(), + }, + ]); }); it('should return correct indices for yearly [logstash-]YYYY', function () { @@ -59,9 +109,60 @@ describe('Index Patterns', function () { var end = moment.utc('2015-02-01'); var interval = { name: 'years', startOf: 'year', display: 'Yearly' }; var list = intervals.toIndexList('[logstash-]YYYY', interval, start, end); - expect(list).to.contain('logstash-2014'); - expect(list).to.contain('logstash-2015'); + expect(list).to.eql([ + { + index: 'logstash-2014', + min: moment.utc(0).year(2014).valueOf(), + max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(), + }, + { + index: 'logstash-2015', + min: moment.utc(0).year(2015).valueOf(), + max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(), + }, + ]); }); + context('with sortDirection=asc', function () { + it('returns values in ascending order', function () { + var start = moment.utc('2014-12-01'); + var end = moment.utc('2015-02-01'); + var interval = { name: 'years', startOf: 'year', display: 'Yearly' }; + var list = intervals.toIndexList('[logstash-]YYYY', interval, start, end, 'asc'); + expect(list).to.eql([ + { + index: 'logstash-2014', + min: moment.utc(0).year(2014).valueOf(), + max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(), + }, + { + index: 'logstash-2015', + min: moment.utc(0).year(2015).valueOf(), + max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(), + }, + ]); + }); + }); + + context('with sortDirection=desc', function () { + it('returns values in descending order', function () { + var start = moment.utc('2014-12-01'); + var end = moment.utc('2015-02-01'); + var interval = { name: 'years', startOf: 'year', display: 'Yearly' }; + var list = intervals.toIndexList('[logstash-]YYYY', interval, start, end, 'desc'); + expect(list).to.eql([ + { + index: 'logstash-2015', + min: moment.utc(0).year(2015).valueOf(), + max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(), + }, + { + index: 'logstash-2014', + min: moment.utc(0).year(2014).valueOf(), + max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(), + }, + ]); + }); + }); }); }); diff --git a/src/ui/public/index_patterns/_calculate_indices.js b/src/ui/public/index_patterns/_calculate_indices.js index 9401e1d923ecf..a8503e0542cbe 100644 --- a/src/ui/public/index_patterns/_calculate_indices.js +++ b/src/ui/public/index_patterns/_calculate_indices.js @@ -31,8 +31,7 @@ define(function (require) { // given time range function calculateIndices(pattern, timeFieldName, start, stop, sortDirection) { return getFieldStats(pattern, timeFieldName, start, stop) - .then(resp => resp.indices) - .then(indices => omitIndicesWithoutTimeField(indices, timeFieldName)) + .then(resp => omitIndicesWithoutTimeField(resp.indices, timeFieldName)) .then(indices => sortIndexStats(indices, timeFieldName, sortDirection)); }; @@ -60,19 +59,19 @@ define(function (require) { } function sortIndexStats(indices, timeFieldName, sortDirection) { - if (!sortDirection) return _.keys(indices); + const desc = sortDirection === 'desc'; + const leader = desc ? 'max' : 'min'; - // FIXME: Once https://github.com/elastic/elasticsearch/issues/14404 is closed - // this should be sorting based on the sortable value of a field. - const edgeKey = sortDirection === 'desc' ? 'max_value' : 'min_value'; + let indexDetails = _(indices).map((stats, index) => { + const field = stats.fields[timeFieldName]; + return { index, min: field.min_value, max: field.max_value }; + }); + + if (sortDirection) { + indexDetails = indexDetails.sortByOrder([leader], [sortDirection]); + } - return _(indices) - .map((stats, index) => ( - { index, edge: stats.fields[timeFieldName][edgeKey] } - )) - .sortByOrder(['edge'], [sortDirection]) - .pluck('index') - .value(); + return indexDetails.value(); } return calculateIndices; diff --git a/src/ui/public/index_patterns/_index_pattern.js b/src/ui/public/index_patterns/_index_pattern.js index 0f14a8d337567..cd79b721cbb4b 100644 --- a/src/ui/public/index_patterns/_index_pattern.js +++ b/src/ui/public/index_patterns/_index_pattern.js @@ -178,23 +178,34 @@ define(function (require) { }; self.toIndexList = function (start, stop, sortDirection) { - return new Promise(function (resolve) { - var indexList; - var interval = self.getInterval(); - - if (interval) { - indexList = intervals.toIndexList(self.id, interval, start, stop); - if (sortDirection === 'desc') indexList = indexList.reverse(); - } else if (self.isWildcard() && self.hasTimeField()) { - indexList = calculateIndices(self.id, self.timeFieldName, start, stop, sortDirection); - } else { - indexList = self.id; + return self + .toDetailedIndexList(start, stop, sortDirection) + .then(detailedIndices => { + if (!_.isArray(detailedIndices)) { + return detailedIndices.index; } - resolve(indexList); + return _.pluck(detailedIndices, 'index'); }); }; + self.toDetailedIndexList = Promise.method(function (start, stop, sortDirection) { + const interval = self.getInterval(); + if (interval) { + return intervals.toIndexList(self.id, interval, start, stop, sortDirection); + } + + if (self.isWildcard() && self.hasTimeField()) { + return calculateIndices(self.id, self.timeFieldName, start, stop, sortDirection); + } + + return { + index: self.id, + min: -Infinity, + max: Infinity, + }; + }); + self.hasTimeField = function () { return !!(this.timeFieldName && this.fields.byName[this.timeFieldName]); }; diff --git a/src/ui/public/index_patterns/_intervals.js b/src/ui/public/index_patterns/_intervals.js index c7f24488e98d2..59374222df136 100644 --- a/src/ui/public/index_patterns/_intervals.js +++ b/src/ui/public/index_patterns/_intervals.js @@ -35,7 +35,7 @@ define(function (require) { ] }); - intervals.toIndexList = function (format, interval, a, b) { + intervals.toIndexList = function (format, interval, a, b, sortDirection) { var bounds; // setup the range that the list will span, return two moment objects that @@ -70,10 +70,20 @@ define(function (require) { // turn stop into milliseconds to that it's not constantly converted by the while condition var stop = range.shift().valueOf(); + var add = sortDirection === 'desc' ? 'unshift' : 'push'; + while (start <= stop) { - indexList.push(start.format(format)); - start.add(1, interval.name); + const index = start.format(format); + const next = moment(start).add(1, interval.name); + const bound = moment(next).subtract(1, 'ms'); + + const min = start.valueOf(); + const max = bound.valueOf(); + indexList[add]({ index, min, max }); + + start = next; } + return indexList; }; From f8811c586f3272666bd3509d2a1bdb6a7f0a6fa9 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 10 Dec 2015 16:54:29 -0700 Subject: [PATCH 2/2] [indexPattern/calcIndices] deal with elastic/elasticsearch#14404 until 2.2 --- src/ui/public/index_patterns/_calculate_indices.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ui/public/index_patterns/_calculate_indices.js b/src/ui/public/index_patterns/_calculate_indices.js index a8503e0542cbe..07643a81b9201 100644 --- a/src/ui/public/index_patterns/_calculate_indices.js +++ b/src/ui/public/index_patterns/_calculate_indices.js @@ -64,7 +64,15 @@ define(function (require) { let indexDetails = _(indices).map((stats, index) => { const field = stats.fields[timeFieldName]; - return { index, min: field.min_value, max: field.max_value }; + + // TODO: remove when we get to es 2.2, see elastic/elasticsearch#14404 + let min = field.min_value; + if (typeof min === 'string') min = moment(min).valueOf(); + + let max = field.max_value; + if (typeof max === 'string') max = moment(max).valueOf(); + + return { index, min, max }; }); if (sortDirection) {