Skip to content

Commit

Permalink
Closes elastic#2394 - Allow relative from and to (elastic#10990)
Browse files Browse the repository at this point in the history
* Closes elastic#2394 - Allow relative from and to

* Closes elastic#6732 - Adding support future realtive for time picker
  • Loading branch information
simianhacker committed Apr 7, 2017
1 parent 0bd7858 commit c85f46d
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 97 deletions.
78 changes: 44 additions & 34 deletions src/ui/public/directives/__tests__/timepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,13 @@ describe('timepicker directive', function () {
});

it('has a preview of the "from" input', function (done) {
const preview = $elem.find('.kbn-timepicker-section span[ng-show="relative.preview"]');
const preview = $elem.find('.kbn-timepicker-section span[ng-show="relative.from.preview"]');
expect(preview.text()).to.be(moment().subtract(15, 'minutes').format($scope.format));
done();
});

it('has a disabled "to" field that contains "Now"', function (done) {
expect($elem.find('.kbn-timepicker-section input[disabled]').val()).to.be('Now');
expect($elem.find('.kbn-timepicker-section span[ng-show="relative.to.preview"]').text()).to.be('Now');
done();
});

Expand All @@ -208,19 +208,19 @@ describe('timepicker directive', function () {
expect(button.length).to.be(0);

// Make the form invalid
$scope.relative.count = -3;
$scope.formatRelative();
$scope.relative.from.count = -3;
$scope.formatRelative('from');
$scope.$digest();

button = $elem.find('button[disabled]');
expect(button.length).to.be(1);
done();
});

it('has a dropdown bound to relative.unit that contains all of the intervals', function (done) {
const select = $elem.find('.kbn-timepicker-section select[ng-model="relative.unit"]');
it('has a dropdown bound to relative.from.unit that contains all of the intervals', function (done) {
const select = $elem.find('.kbn-timepicker-section select[ng-model="relative.from.unit"]');
expect(select.length).to.be(1);
expect(select.find('option').length).to.be(7);
expect(select.find('option').length).to.be(14);

// Check each relative option, make sure it is in the list
_.each($scope.relativeOptions, function (unit, i) {
Expand All @@ -230,14 +230,14 @@ describe('timepicker directive', function () {
});

it('has a checkbox that is checked when rounding is enabled', function (done) {
const checkbox = $elem.find('.kbn-timepicker-section input[ng-model="relative.round"]');
const checkbox = $elem.find('.kbn-timepicker-section input[ng-model="relative.from.round"]');
expect(checkbox.length).to.be(1);

// Rounding is disabled by default
expect(checkbox.prop('checked')).to.be(false);

// Enable rounding
$scope.relative.round = true;
$scope.relative.from.round = true;
$scope.$digest();
expect(checkbox.prop('checked')).to.be(true);

Expand All @@ -246,78 +246,88 @@ describe('timepicker directive', function () {

it('rounds the preview down to the unit when rounding is enabled', function (done) {
// Enable rounding
$scope.relative.round = true;
$scope.relative.count = 0;
$scope.relative.from.round = true;
$scope.relative.from.count = 0;

_.each($scope.units, function (longUnit, shortUnit) {
$scope.relative.count = 0;
$scope.relative.unit = shortUnit;
$scope.formatRelative();
$scope.relative.from.count = 0;
$scope.relative.from.unit = shortUnit;
$scope.formatRelative('from');

// The preview should match the start of the unit eg, the start of the minute
expect($scope.relative.preview).to.be(moment().startOf(longUnit).format($scope.format));
expect($scope.relative.from.preview).to.be(moment().startOf(longUnit).format($scope.format));
});

done();
});

it('does not round the preview down to the unit when rounding is disable', function (done) {
// Disable rounding
$scope.relative.round = false;
$scope.relative.count = 0;
$scope.relative.from.round = false;
$scope.relative.from.count = 1;

_.each($scope.units, function (longUnit, shortUnit) {
$scope.relative.unit = shortUnit;
$scope.formatRelative();
$scope.relative.from.unit = shortUnit;
$scope.formatRelative('from');

const matches = shortUnit.match(/([smhdwMy])(\+)?/);
let unit;
let opp = '-';
if (matches) {
unit = matches[1];
opp = matches[2];
}

const fn = opp === '+' ? 'add' : 'subtract';

// The preview should match the start of the unit eg, the start of the minute
expect($scope.relative.preview).to.be(moment().format($scope.format));
expect($scope.relative.from.preview).to.be(moment()[fn](1, unit).format($scope.format));
});

done();
});

it('has a $scope.applyRelative() that sets from and to based on relative.round, relative.count and relative.unit', function (done) {
// Disable rounding
$scope.relative.round = false;
$scope.relative.count = 1;
$scope.relative.unit = 's';
$scope.relative.from.round = false;
$scope.relative.from.count = 1;
$scope.relative.from.unit = 's';
$scope.applyRelative();
sinon.assert.calledOnce($parentScope.updateFilter);
expect($parentScope.updateFilter.getCall(0).args[0]).to.be('now-1s');

$scope.relative.count = 2;
$scope.relative.unit = 'm';
$scope.relative.from.count = 2;
$scope.relative.from.unit = 'm';
$scope.applyRelative();
expect($parentScope.updateFilter.getCall(1).args[0]).to.be('now-2m');

$scope.relative.count = 3;
$scope.relative.unit = 'h';
$scope.relative.from.count = 3;
$scope.relative.from.unit = 'h';
$scope.applyRelative();
expect($parentScope.updateFilter.getCall(2).args[0]).to.be('now-3h');

// Enable rounding
$scope.relative.round = true;
$scope.relative.count = 7;
$scope.relative.unit = 'd';
$scope.relative.from.round = true;
$scope.relative.from.count = 7;
$scope.relative.from.unit = 'd';
$scope.applyRelative();
expect($parentScope.updateFilter.getCall(3).args[0]).to.be('now-7d/d');

done();
});

it('updates the input fields when the scope variables are changed', function (done) {
const input = $elem.find('.kbn-timepicker-section input[ng-model="relative.count"]');
const select = $elem.find('.kbn-timepicker-section select[ng-model="relative.unit"]');
const input = $elem.find('.kbn-timepicker-section input[ng-model="relative.from.count"]');
const select = $elem.find('.kbn-timepicker-section select[ng-model="relative.from.unit"]');

$scope.relative.count = 5;
$scope.relative.from.count = 5;
$scope.$digest();
expect(input.val()).to.be('5');


// Should update the selected option
_.each($scope.units, function (longUnit, shortUnit) {
$scope.relative.unit = shortUnit;
$scope.relative.from.unit = shortUnit;
$scope.$digest();

expect(select.val().split(':')[1]).to.be(shortUnit);
Expand Down
105 changes: 105 additions & 0 deletions src/ui/public/timepicker/__tests__/parse_relative_parts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { parseRelativeString, parseRelativeParts } from '../parse_relative_parts';
import expect from 'expect.js';
import moment from 'moment';

describe('parseRelativeParts(from, to, relativeOptions)', () => {

it('should parse relative string', () => {
const results = parseRelativeString('now-2h');
expect(results).to.have.property('count', 2);
expect(results).to.have.property('unit', 'h');
expect(results).to.have.property('round', false);
});

it('should parse now', () => {
const results = parseRelativeString('now');
expect(results).to.have.property('count', 0);
expect(results).to.have.property('unit', 's');
expect(results).to.have.property('round', false);
});

it('should parse set round options', () => {
const results = parseRelativeString('now-2h/h');
expect(results).to.have.property('round', true);
});

it('should parse now-2h to now-10m/m', () => {
expect(parseRelativeParts('now-2h', 'now-10m/m')).to.eql({
from: {
count: 2,
unit: 'h',
round: false
},
to: {
count: 10,
unit: 'm',
round: true
}
});
});

it('should parse now-2h to now+10m/m', () => {
expect(parseRelativeParts('now-2h', 'now+10m/m')).to.eql({
from: {
count: 2,
unit: 'h',
round: false
},
to: {
count: 10,
unit: 'm+',
round: true
}
});
});

it('should parse 3 months ago to now', () => {
expect(parseRelativeParts(moment().subtract(3, 'M'), moment())).to.eql({
from: {
count: 3,
unit: 'M',
round: false
},
to: {
count: 0,
unit: 's',
round: false
}
});
});

it('should parse 3 months ago to 15 minutes ago', () => {
const from = moment().subtract(3, 'M');
const to = moment().subtract(15, 'm');
expect(parseRelativeParts(from, to)).to.eql({
from: {
count: 3,
unit: 'M',
round: false
},
to: {
count: 15,
unit: 'm',
round: false
}
});
});

it('should parse 3 months ago to 2 hours from now', () => {
const from = moment().subtract(3, 'M');
const to = moment().add(2, 'h');
expect(parseRelativeParts(from, to)).to.eql({
from: {
count: 3,
unit: 'M',
round: false
},
to: {
count: 2,
unit: 'h+',
round: false
}
});
});

});
54 changes: 54 additions & 0 deletions src/ui/public/timepicker/parse_relative_parts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import dateMath from '@elastic/datemath';
import moment from 'moment';
import _ from 'lodash';
import { relativeOptions } from './relative_options';

export function parseRelativeString(part) {
let results = {};
const matches = _.isString(part) && part.match(/now(([\-\+])([0-9]+)([smhdwMy])(\/[smhdwMy])?)?/);

const isNow = matches && !matches[1];
const opperator = matches && matches[2];
const count = matches && matches[3];
const unit = matches && matches[4];
const roundBy = matches && matches[5];

if (isNow) {
return { count: 0, unit: 's', round: false };
}

if (count && unit) {
results.count = parseInt(count, 10);
results.unit = unit;
if (opperator === '+') results.unit += '+';
results.round = roundBy ? true : false;
return results;

} else {
results = { count: 0, unit: 's', round: false };
const duration = moment.duration(moment().diff(dateMath.parse(part)));
const units = _.pluck(_.clone(relativeOptions).reverse(), 'value')
.filter(s => /^[smhdwMy]$/.test(s));
let unitOp = '';
for (let i = 0; i < units.length; i++) {
const as = duration.as(units[i]);
if (as < 0) unitOp = '+';
if (Math.abs(as) > 1) {
results.count = Math.round(Math.abs(as));
results.unit = units[i] + unitOp;
results.round = false;
break;
}
}
return results;
}


}

export function parseRelativeParts(from, to) {
const results = {};
results.from = parseRelativeString(from);
results.to = parseRelativeString(to);
if (results.from && results.to) return results;
}
18 changes: 18 additions & 0 deletions src/ui/public/timepicker/relative_options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const relativeOptions = [
{ text: 'Seconds ago', value: 's' },
{ text: 'Minutes ago', value: 'm' },
{ text: 'Hours ago', value: 'h' },
{ text: 'Days ago', value: 'd' },
{ text: 'Weeks ago', value: 'w' },
{ text: 'Months ago', value: 'M' },
{ text: 'Years ago', value: 'y' },

{ text: 'Seconds from now', value: 's+' },
{ text: 'Minutes from now', value: 'm+' },
{ text: 'Hours from now', value: 'h+' },
{ text: 'Days from now', value: 'd+' },
{ text: 'Weeks from now', value: 'w+' },
{ text: 'Months from now', value: 'M+' },
{ text: 'Years from now', value: 'y+' },

];
Loading

0 comments on commit c85f46d

Please sign in to comment.