[npm] prepare @kbn/datemath for publishing (#26559)

We need to share `@kbn/datemath` with `@elastic/eui`, and rather than making them rely on Kibana for their dependencies we've decided to republish `@kbn/datemath` as `@elastic/datemath`. This isn't something we want to do often, so please check with the platform team if you'd like to do this for another module.
This commit is contained in:
Spencer 2018-12-03 16:01:16 -08:00 committed by GitHub
parent 168cb07cd2
commit 0b4ae5020b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 43 additions and 49 deletions

View file

@ -1,13 +0,0 @@
{
"presets": [["env", {
"targets": {
"node": "current",
"browsers": [
"last 2 versions",
"> 5%",
"Safari 7",
]
}
}]],
"plugins": ["add-module-exports"]
}

View file

@ -1,23 +0,0 @@
{
"name": "@kbn/datemath",
"version": "5.0.0",
"description": "elasticsearch datemath parser, used in kibana",
"license": "Apache-2.0",
"private": true,
"main": "target/index.js",
"typings": "target/index.d.ts",
"scripts": {
"build": "babel src --out-dir target --copy-files",
"kbn:bootstrap": "yarn build --quiet",
"kbn:watch": "yarn build --watch"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-env": "^1.6.1"
},
"dependencies": {
"moment": "^2.13.0",
"tslib": "^1.9.3"
}
}

View file

@ -1,3 +0,0 @@
# datemath
Datemath string parser used in Kibana

View file

@ -1,45 +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.
*/
import moment from 'moment';
export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y';
declare const datemath: {
unitsMap: {
[k in Unit]: {
weight: number;
type: 'calendar' | 'fixed' | 'mixed';
base: number;
}
};
units: Unit[];
unitsAsc: Unit[];
unitsDesc: Unit[];
parse(
input: string,
options?: {
roundUp?: boolean;
forceNow?: boolean;
momentInstance?: typeof moment;
}
): moment.Moment | undefined;
};
export default datemath;

View file

@ -1,160 +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.
*/
import moment from 'moment';
const unitsMap = {
ms: { weight: 1, type: 'fixed', base: 1 },
s: { weight: 2, type: 'fixed', base: 1000 },
m: { weight: 3, type: 'mixed', base: 1000 * 60 },
h: { weight: 4, type: 'mixed', base: 1000 * 60 * 60 },
d: { weight: 5, type: 'mixed', base: 1000 * 60 * 60 * 24 },
w: { weight: 6, type: 'calendar', base: NaN },
M: { weight: 7, type: 'calendar', base: NaN },
// q: { weight: 8, type: 'calendar' }, // TODO: moment duration does not support quarter
y: { weight: 9, type: 'calendar', base: NaN },
};
const units = Object.keys(unitsMap).sort((a, b) => unitsMap[b].weight - unitsMap[a].weight);
const unitsDesc = [...units];
const unitsAsc = [...units].reverse();
const isDate = d => Object.prototype.toString.call(d) === '[object Date]';
const isValidDate = d => isDate(d) && !isNaN(d.valueOf());
/*
* This is a simplified version of elasticsearch's date parser.
* If you pass in a momentjs instance as the third parameter the calculation
* will be done using this (and its locale settings) instead of the one bundled
* with this library.
*/
function parse(text, { roundUp = false, momentInstance = moment, forceNow } = {}) {
if (!text) return undefined;
if (momentInstance.isMoment(text)) return text;
if (isDate(text)) return momentInstance(text);
if (forceNow !== undefined && !isValidDate(forceNow)) {
throw new Error('forceNow must be a valid Date');
}
let time;
let mathString = '';
let index;
let parseString;
if (text.substring(0, 3) === 'now') {
time = momentInstance(forceNow);
mathString = text.substring('now'.length);
} else {
index = text.indexOf('||');
if (index === -1) {
parseString = text;
mathString = ''; // nothing else
} else {
parseString = text.substring(0, index);
mathString = text.substring(index + 2);
}
// We're going to just require ISO8601 timestamps, k?
time = momentInstance(parseString);
}
if (!mathString.length) {
return time;
}
return parseDateMath(mathString, time, roundUp);
}
function parseDateMath(mathString, time, roundUp) {
const dateTime = time;
const len = mathString.length;
let i = 0;
while (i < len) {
const c = mathString.charAt(i++);
let type;
let num;
let unit;
if (c === '/') {
type = 0;
} else if (c === '+') {
type = 1;
} else if (c === '-') {
type = 2;
} else {
return;
}
if (isNaN(mathString.charAt(i))) {
num = 1;
} else if (mathString.length === 2) {
num = mathString.charAt(i);
} else {
const numFrom = i;
while (!isNaN(mathString.charAt(i))) {
i++;
if (i >= len) return;
}
num = parseInt(mathString.substring(numFrom, i), 10);
}
if (type === 0) {
// rounding is only allowed on whole, single, units (eg M or 1M, not 0.5M or 2M)
if (num !== 1) {
return;
}
}
unit = mathString.charAt(i++);
// append additional characters in the unit
for (let j = i; j < len; j++) {
const unitChar = mathString.charAt(i);
if (/[a-z]/i.test(unitChar)) {
unit += unitChar;
i++;
} else {
break;
}
}
if (units.indexOf(unit) === -1) {
return;
} else {
if (type === 0) {
if (roundUp) dateTime.endOf(unit);
else dateTime.startOf(unit);
} else if (type === 1) {
dateTime.add(num, unit);
} else if (type === 2) {
dateTime.subtract(num, unit);
}
}
}
return dateTime;
}
export default {
parse: parse,
unitsMap: Object.freeze(unitsMap),
units: Object.freeze(units),
unitsAsc: Object.freeze(unitsAsc),
unitsDesc: Object.freeze(unitsDesc),
};

View file

@ -1,394 +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.
*/
import dateMath from '../src/index';
import moment from 'moment';
import sinon from 'sinon';
import expect from 'expect.js';
/**
* Require a new instance of the moment library, bypassing the require cache.
* This is needed, since we are trying to test whether or not this library works
* when passing in a different configured moment instance. If we would change
* the locales on the imported moment, it would automatically apply
* to the source code, even without passing it in to the method, since they share
* the same global state. This method avoids this, by loading a separate instance
* of moment, by deleting the require cache and require the library again.
*/
function momentClone() {
delete require.cache[require.resolve('moment')];
return require('moment');
}
describe('dateMath', function() {
// Test each of these intervals when testing relative time
const spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y', 'ms'];
const anchor = '2014-01-01T06:06:06.666Z';
const anchoredDate = new Date(Date.parse(anchor));
const unix = moment(anchor).valueOf();
const format = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
let clock;
describe('errors', function() {
it('should return undefined if passed something falsy', function() {
expect(dateMath.parse()).to.be(undefined);
});
it('should return undefined if I pass an operator besides [+-/]', function() {
expect(dateMath.parse('now&1d')).to.be(undefined);
});
it('should return undefined if I pass a unit besides' + spans.toString(), function() {
expect(dateMath.parse('now+5f')).to.be(undefined);
});
it('should return undefined if rounding unit is not 1', function() {
expect(dateMath.parse('now/2y')).to.be(undefined);
expect(dateMath.parse('now/0.5y')).to.be(undefined);
});
it('should not go into an infinite loop when missing a unit', function() {
expect(dateMath.parse('now-0')).to.be(undefined);
expect(dateMath.parse('now-00')).to.be(undefined);
expect(dateMath.parse('now-000')).to.be(undefined);
});
describe('forceNow', function() {
it('should throw an Error if passed a string', function() {
const fn = () => dateMath.parse('now', { forceNow: '2000-01-01T00:00:00.000Z' });
expect(fn).to.throwError();
});
it('should throw an Error if passed a moment', function() {
expect(() => dateMath.parse('now', { forceNow: moment() })).to.throwError();
});
it('should throw an Error if passed an invalid date', function() {
expect(() => dateMath.parse('now', { forceNow: new Date('foobar') })).to.throwError();
});
});
});
describe('objects and strings', function() {
let mmnt;
let date;
let string;
let now;
beforeEach(function() {
clock = sinon.useFakeTimers(unix);
now = moment();
mmnt = moment(anchor);
date = mmnt.toDate();
string = mmnt.format(format);
});
afterEach(function() {
clock.restore();
});
it('should return the same moment if passed a moment', function() {
expect(dateMath.parse(mmnt)).to.eql(mmnt);
});
it('should return a moment if passed a date', function() {
expect(dateMath.parse(date).format(format)).to.eql(mmnt.format(format));
});
it('should return a moment if passed an ISO8601 string', function() {
expect(dateMath.parse(string).format(format)).to.eql(mmnt.format(format));
});
it('should return the current time when parsing now', function() {
expect(dateMath.parse('now').format(format)).to.eql(now.format(format));
});
it('should use the forceNow parameter when parsing now', function() {
expect(dateMath.parse('now', { forceNow: anchoredDate }).valueOf()).to.eql(unix);
});
});
describe('subtraction', function() {
let now;
let anchored;
beforeEach(function() {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function() {
clock.restore();
});
[5, 12, 247].forEach(len => {
spans.forEach(span => {
const nowEx = `now-${len}${span}`;
const thenEx = `${anchor}||-${len}${span}`;
it('should return ' + len + span + ' ago', function() {
const parsed = dateMath.parse(nowEx).format(format);
expect(parsed).to.eql(now.subtract(len, span).format(format));
});
it('should return ' + len + span + ' before ' + anchor, function() {
const parsed = dateMath.parse(thenEx).format(format);
expect(parsed).to.eql(anchored.subtract(len, span).format(format));
});
it('should return ' + len + span + ' before forceNow', function() {
const parsed = dateMath.parse(nowEx, { forceNow: anchoredDate }).valueOf();
expect(parsed).to.eql(anchored.subtract(len, span).valueOf());
});
});
});
});
describe('addition', function() {
let now;
let anchored;
beforeEach(function() {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function() {
clock.restore();
});
[5, 12, 247].forEach(len => {
spans.forEach(span => {
const nowEx = `now+${len}${span}`;
const thenEx = `${anchor}||+${len}${span}`;
it('should return ' + len + span + ' from now', function() {
expect(dateMath.parse(nowEx).format(format)).to.eql(now.add(len, span).format(format));
});
it('should return ' + len + span + ' after ' + anchor, function() {
expect(dateMath.parse(thenEx).format(format)).to.eql(
anchored.add(len, span).format(format)
);
});
it('should return ' + len + span + ' after forceNow', function() {
expect(dateMath.parse(nowEx, { forceNow: anchoredDate }).valueOf()).to.eql(
anchored.add(len, span).valueOf()
);
});
});
});
});
describe('rounding', function() {
let now;
let anchored;
beforeEach(function() {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function() {
clock.restore();
});
spans.forEach(span => {
it(`should round now to the beginning of the ${span}`, function() {
expect(dateMath.parse('now/' + span).format(format)).to.eql(
now.startOf(span).format(format)
);
});
it(`should round now to the beginning of forceNow's ${span}`, function() {
expect(dateMath.parse('now/' + span, { forceNow: anchoredDate }).valueOf()).to.eql(
anchored.startOf(span).valueOf()
);
});
it(`should round now to the end of the ${span}`, function() {
expect(dateMath.parse('now/' + span, { roundUp: true }).format(format)).to.eql(
now.endOf(span).format(format)
);
});
it(`should round now to the end of forceNow's ${span}`, function() {
expect(
dateMath.parse('now/' + span, { roundUp: true, forceNow: anchoredDate }).valueOf()
).to.eql(anchored.endOf(span).valueOf());
});
});
});
describe('math and rounding', function() {
let now;
let anchored;
beforeEach(function() {
clock = sinon.useFakeTimers(unix);
now = moment();
anchored = moment(anchor);
});
afterEach(function() {
clock.restore();
});
it('should round to the nearest second with 0 value', function() {
const val = dateMath.parse('now-0s/s').format(format);
expect(val).to.eql(now.startOf('s').format(format));
});
it('should subtract 17s, rounded to the nearest second', function() {
const val = dateMath.parse('now-17s/s').format(format);
expect(val).to.eql(
now
.startOf('s')
.subtract(17, 's')
.format(format)
);
});
it('should add 555ms, rounded to the nearest millisecond', function() {
const val = dateMath.parse('now+555ms/ms').format(format);
expect(val).to.eql(
now
.add(555, 'ms')
.startOf('ms')
.format(format)
);
});
it('should subtract 555ms, rounded to the nearest second', function() {
const val = dateMath.parse('now-555ms/s').format(format);
expect(val).to.eql(
now
.subtract(555, 'ms')
.startOf('s')
.format(format)
);
});
it('should round weeks to Sunday by default', function() {
const val = dateMath.parse('now-1w/w');
expect(val.isoWeekday()).to.eql(7);
});
it('should round weeks based on the passed moment locale start of week setting', function() {
const m = momentClone();
// Define a locale, that has Tuesday as beginning of the week
m.defineLocale('x-test', {
week: { dow: 2 },
});
const val = dateMath.parse('now-1w/w', { momentInstance: m });
expect(val.isoWeekday()).to.eql(2);
});
it('should round up weeks based on the passed moment locale start of week setting', function() {
const m = momentClone();
// Define a locale, that has Tuesday as beginning of the week
m.defineLocale('x-test', {
week: { dow: 3 },
});
const val = dateMath.parse('now-1w/w', {
roundUp: true,
momentInstance: m,
});
// The end of the range (rounding up) should be the last day of the week (so one day before)
// our start of the week, that's why 3 - 1
expect(val.isoWeekday()).to.eql(3 - 1);
});
it('should round relative to forceNow', function() {
const val = dateMath.parse('now-0s/s', { forceNow: anchoredDate }).valueOf();
expect(val).to.eql(anchored.startOf('s').valueOf());
});
it('should parse long expressions', () => {
expect(dateMath.parse('now-1d/d+8h+50m')).to.be.ok();
});
});
describe('used momentjs instance', function() {
it('should use the default moment instance if parameter not specified', function() {
const momentSpy = sinon.spy(moment, 'isMoment');
dateMath.parse('now');
expect(momentSpy.called).to.be(true);
momentSpy.restore();
});
it('should not use default moment instance if parameter is specified', function() {
const m = momentClone();
const momentSpy = sinon.spy(moment, 'isMoment');
const cloneSpy = sinon.spy(m, 'isMoment');
dateMath.parse('now', { momentInstance: m });
expect(momentSpy.called).to.be(false);
expect(cloneSpy.called).to.be(true);
momentSpy.restore();
cloneSpy.restore();
});
it('should work with multiple different instances', function() {
const m1 = momentClone();
const m2 = momentClone();
const m1Spy = sinon.spy(m1, 'isMoment');
const m2Spy = sinon.spy(m2, 'isMoment');
dateMath.parse('now', { momentInstance: m1 });
expect(m1Spy.called).to.be(true);
expect(m2Spy.called).to.be(false);
m1Spy.resetHistory();
m2Spy.resetHistory();
dateMath.parse('now', { momentInstance: m2 });
expect(m1Spy.called).to.be(false);
expect(m2Spy.called).to.be(true);
m1Spy.restore();
m2Spy.restore();
});
it('should use global instance after passing an instance', function() {
const m = momentClone();
const momentSpy = sinon.spy(moment, 'isMoment');
const cloneSpy = sinon.spy(m, 'isMoment');
dateMath.parse('now', { momentInstance: m });
expect(momentSpy.called).to.be(false);
expect(cloneSpy.called).to.be(true);
momentSpy.resetHistory();
cloneSpy.resetHistory();
dateMath.parse('now');
expect(momentSpy.called).to.be(true);
expect(cloneSpy.called).to.be(false);
momentSpy.restore();
cloneSpy.restore();
});
});
describe('units', function() {
it('should have units descending for unitsDesc', function() {
expect(dateMath.unitsDesc).to.eql(['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']);
});
it('should have units ascending for unitsAsc', function() {
expect(dateMath.unitsAsc).to.eql(['ms', 's', 'm', 'h', 'd', 'w', 'M', 'y']);
});
});
});

View file

@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "./target"
},
"include": [
"./src/**/*.ts"
]
}