From a31bd012c34dfd4aca7947638bac4237355fe6f6 Mon Sep 17 00:00:00 2001 From: Matt Provost Date: Tue, 20 Jun 2023 14:25:27 -0700 Subject: [PATCH] Replace @elastic/datemath with @opensearch/datemath (#204) * Replace @elastic/datemath with @opensearch/datemath Signed-off-by: Matt Provost * Clean up Signed-off-by: Matt Provost --------- Signed-off-by: Matt Provost --- i18ntokens.json | 76 ++-- package.json | 3 +- packages/opensearch-datemath/.npmignore | 2 + packages/opensearch-datemath/index.d.ts | 62 +++ packages/opensearch-datemath/index.js | 171 ++++++++ packages/opensearch-datemath/index.test.ts | 393 ++++++++++++++++++ packages/opensearch-datemath/package.json | 11 + packages/opensearch-datemath/readme.md | 3 + packages/opensearch-datemath/tsconfig.json | 9 + src-docs/src/components/codesandbox/link.js | 2 +- .../src/views/guidelines/getting_started.md | 4 +- .../super_date_picker_example.js | 2 +- .../super_date_picker/date_modes.test.ts | 4 +- .../super_date_picker/date_modes.ts | 2 +- .../date_popover/absolute_tab.tsx | 2 +- .../date_popover/relative_tab.tsx | 2 +- .../super_date_picker/pretty_duration.ts | 2 +- .../quick_select_popover/quick_select.tsx | 2 +- .../quick_select_utils.ts | 2 +- .../super_date_picker/relative_utils.ts | 2 +- .../super_date_picker/super_date_picker.tsx | 2 +- wiki/consuming.md | 2 +- yarn.lock | 10 +- 23 files changed, 708 insertions(+), 62 deletions(-) create mode 100644 packages/opensearch-datemath/.npmignore create mode 100644 packages/opensearch-datemath/index.d.ts create mode 100644 packages/opensearch-datemath/index.js create mode 100644 packages/opensearch-datemath/index.test.ts create mode 100644 packages/opensearch-datemath/package.json create mode 100644 packages/opensearch-datemath/readme.md create mode 100644 packages/opensearch-datemath/tsconfig.json diff --git a/i18ntokens.json b/i18ntokens.json index 867bed764e..96c4356734 100644 --- a/i18ntokens.json +++ b/i18ntokens.json @@ -187,12 +187,12 @@ "start": { "line": 191, "column": 8, - "index": 5650 + "index": 5638 }, "end": { "line": 193, "column": 40, - "index": 5750 + "index": 5738 } }, "filepath": "src/components/bottom_bar/bottom_bar.tsx" @@ -205,12 +205,12 @@ "start": { "line": 217, "column": 14, - "index": 6647 + "index": 6635 }, "end": { "line": 221, "column": 16, - "index": 6920 + "index": 6908 } }, "filepath": "src/components/bottom_bar/bottom_bar.tsx" @@ -223,12 +223,12 @@ "start": { "line": 223, "column": 14, - "index": 6953 + "index": 6941 }, "end": { "line": 226, "column": 16, - "index": 7150 + "index": 7138 } }, "filepath": "src/components/bottom_bar/bottom_bar.tsx" @@ -1861,12 +1861,12 @@ "start": { "line": 146, "column": 12, - "index": 4299 + "index": 4302 }, "end": { "line": 151, "column": 62, - "index": 4513 + "index": 4516 } }, "filepath": "src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx" @@ -1879,12 +1879,12 @@ "start": { "line": 146, "column": 12, - "index": 4299 + "index": 4302 }, "end": { "line": 151, "column": 62, - "index": 4513 + "index": 4516 } }, "filepath": "src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx" @@ -1897,12 +1897,12 @@ "start": { "line": 170, "column": 12, - "index": 5261 + "index": 5264 }, "end": { "line": 172, "column": 43, - "index": 5365 + "index": 5368 } }, "filepath": "src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx" @@ -1915,12 +1915,12 @@ "start": { "line": 189, "column": 8, - "index": 5904 + "index": 5907 }, "end": { "line": 192, "column": 75, - "index": 6075 + "index": 6078 } }, "filepath": "src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx" @@ -1933,12 +1933,12 @@ "start": { "line": 210, "column": 14, - "index": 6566 + "index": 6569 }, "end": { "line": 214, "column": 16, - "index": 6757 + "index": 6760 } }, "filepath": "src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx" @@ -1951,12 +1951,12 @@ "start": { "line": 220, "column": 12, - "index": 6907 + "index": 6910 }, "end": { "line": 224, "column": 14, - "index": 7088 + "index": 7091 } }, "filepath": "src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx" @@ -1987,12 +1987,12 @@ "start": { "line": 194, "column": 8, - "index": 5693 + "index": 5696 }, "end": { "line": 196, "column": 46, - "index": 5792 + "index": 5795 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2005,12 +2005,12 @@ "start": { "line": 213, "column": 12, - "index": 6419 + "index": 6422 }, "end": { "line": 215, "column": 37, - "index": 6519 + "index": 6522 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2023,12 +2023,12 @@ "start": { "line": 226, "column": 16, - "index": 6926 + "index": 6929 }, "end": { "line": 228, "column": 49, - "index": 7039 + "index": 7042 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2041,12 +2041,12 @@ "start": { "line": 241, "column": 16, - "index": 7523 + "index": 7526 }, "end": { "line": 243, "column": 45, - "index": 7628 + "index": 7631 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2059,12 +2059,12 @@ "start": { "line": 261, "column": 12, - "index": 8244 + "index": 8247 }, "end": { "line": 261, "column": 76, - "index": 8308 + "index": 8311 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2077,12 +2077,12 @@ "start": { "line": 276, "column": 12, - "index": 8820 + "index": 8823 }, "end": { "line": 276, "column": 76, - "index": 8884 + "index": 8887 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2095,12 +2095,12 @@ "start": { "line": 290, "column": 12, - "index": 9356 + "index": 9359 }, "end": { "line": 290, "column": 74, - "index": 9418 + "index": 9421 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2113,12 +2113,12 @@ "start": { "line": 311, "column": 14, - "index": 10196 + "index": 10199 }, "end": { "line": 311, "column": 76, - "index": 10258 + "index": 10261 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2131,12 +2131,12 @@ "start": { "line": 318, "column": 12, - "index": 10451 + "index": 10454 }, "end": { "line": 326, "column": 14, - "index": 10725 + "index": 10728 } }, "filepath": "src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx" @@ -2239,12 +2239,12 @@ "start": { "line": 429, "column": 14, - "index": 11289 + "index": 11292 }, "end": { "line": 432, "column": 16, - "index": 11415 + "index": 11418 } }, "filepath": "src/components/date_picker/super_date_picker/super_date_picker.tsx" diff --git a/package.json b/package.json index 6f4d1f6f4c..0d634c2310 100644 --- a/package.json +++ b/package.json @@ -143,8 +143,8 @@ "@babel/preset-react": "^7.10.4", "@babel/preset-typescript": "^7.12.1", "@elastic/charts": "^30.2.0", - "@elastic/datemath": "^5.0.3", "@elastic/eslint-config-kibana": "^0.15.0", + "@opensearch/datemath": "file:./packages/opensearch-datemath", "@svgr/core": "^8.0.0", "@svgr/plugin-svgo": "^8.0.1", "@svgr/plugin-jsx": "^8.0.1", @@ -251,7 +251,6 @@ "yo": "^4.3.1" }, "peerDependencies": { - "@elastic/datemath": "^5.0.2", "@types/react": "^16.9.34", "@types/react-dom": "^16.9.6", "moment": "^2.13.0", diff --git a/packages/opensearch-datemath/.npmignore b/packages/opensearch-datemath/.npmignore new file mode 100644 index 0000000000..591be7afd1 --- /dev/null +++ b/packages/opensearch-datemath/.npmignore @@ -0,0 +1,2 @@ +/tsconfig.json +/__tests__ diff --git a/packages/opensearch-datemath/index.d.ts b/packages/opensearch-datemath/index.d.ts new file mode 100644 index 0000000000..d5a38f0176 --- /dev/null +++ b/packages/opensearch-datemath/index.d.ts @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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[]; + + /** + * Parses a string into a moment object. The string can be something like "now - 15m". + * @param options.forceNow If this optional parameter is supplied, "now" will be treated as this + * date, rather than the real "now". + */ + parse( + input: string, + options?: { + roundUp?: boolean; + forceNow?: Date; + momentInstance?: typeof moment; + } + ): moment.Moment | undefined; +}; + +// eslint-disable-next-line import/no-default-export +export default datemath; diff --git a/packages/opensearch-datemath/index.js b/packages/opensearch-datemath/index.js new file mode 100644 index 0000000000..4367949d7c --- /dev/null +++ b/packages/opensearch-datemath/index.js @@ -0,0 +1,171 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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. + */ + +const moment = require('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 opensearch'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; +} + +module.exports = { + parse: parse, + unitsMap: Object.freeze(unitsMap), + units: Object.freeze(units), + unitsAsc: Object.freeze(unitsAsc), + unitsDesc: Object.freeze(unitsDesc), +}; diff --git a/packages/opensearch-datemath/index.test.ts b/packages/opensearch-datemath/index.test.ts new file mode 100644 index 0000000000..e293da72ac --- /dev/null +++ b/packages/opensearch-datemath/index.test.ts @@ -0,0 +1,393 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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 './index'; +import moment from 'moment'; +import sinon from 'sinon'; +import expect from '@osd/expect'; + +/** + * 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() { + jest.resetModules(); + 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 () { + // @ts-ignore bad arg + 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 () { + // @ts-ignore bad arg + 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']); + }); + }); +}); diff --git a/packages/opensearch-datemath/package.json b/packages/opensearch-datemath/package.json new file mode 100644 index 0000000000..39dfcec403 --- /dev/null +++ b/packages/opensearch-datemath/package.json @@ -0,0 +1,11 @@ +{ + "name": "@opensearch/datemath", + "version": "5.0.3", + "description": "opensearch datemath parser, used in OpenSearch Dashboards", + "license": "Apache-2.0", + "main": "index.js", + "typings": "index.d.ts", + "peerDependencies": { + "moment": "^2.24.0" + } +} diff --git a/packages/opensearch-datemath/readme.md b/packages/opensearch-datemath/readme.md new file mode 100644 index 0000000000..c5d739d13e --- /dev/null +++ b/packages/opensearch-datemath/readme.md @@ -0,0 +1,3 @@ +# datemath + +Datemath string parser used in OpenSearch Dashboards diff --git a/packages/opensearch-datemath/tsconfig.json b/packages/opensearch-datemath/tsconfig.json new file mode 100644 index 0000000000..7a65f45500 --- /dev/null +++ b/packages/opensearch-datemath/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/opensearch-datemath" + }, + "include": [ + "index.d.ts" + ] +} \ No newline at end of file diff --git a/src-docs/src/components/codesandbox/link.js b/src-docs/src/components/codesandbox/link.js index 6c2ef46f21..addecf041b 100644 --- a/src-docs/src/components/codesandbox/link.js +++ b/src-docs/src/components/codesandbox/link.js @@ -152,7 +152,7 @@ ${exampleClose} dependencies: { '@opensearch-project/oui': pkg.version, ...[ - '@elastic/datemath', + '@opensearch/datemath', 'moment', 'react', 'react-dom', diff --git a/src-docs/src/views/guidelines/getting_started.md b/src-docs/src/views/guidelines/getting_started.md index cb14d3838d..c7fe7e388c 100644 --- a/src-docs/src/views/guidelines/getting_started.md +++ b/src-docs/src/views/guidelines/getting_started.md @@ -9,7 +9,7 @@ yarn add @opensearch-project/oui Note that OUI has [several `peerDependencies` requirements](package.json) that will also need to be installed if starting with a blank project. You can read more about other ways to [consume OUI][consuming]. ```js -yarn add @opensearch-project/oui @elastic/datemath moment prop-types +yarn add @opensearch-project/oui moment prop-types ``` @@ -45,7 +45,7 @@ yarn start --port 9000 OUI expects that you polyfill ES2015 features, e.g. [`babel-polyfill`](https://babeljs.io/docs/usage/polyfill/). Without an ES2015 polyfill your app might throw errors on certain browsers. -OUI also has `moment` and `@elastic/datemath` as dependencies itself. These are already loaded in most OpenSearch repos, but make sure to install them if you are starting from scratch. +OUI also has `moment` as a dependency itself. This is already loaded in most OpenSearch repos, but make sure to install it if you are starting from scratch. ## What's available diff --git a/src-docs/src/views/super_date_picker/super_date_picker_example.js b/src-docs/src/views/super_date_picker/super_date_picker_example.js index 8a5c5c4711..4f13e9b9f0 100644 --- a/src-docs/src/views/super_date_picker/super_date_picker_example.js +++ b/src-docs/src/views/super_date_picker/super_date_picker_example.js @@ -90,7 +90,7 @@ export const SuperDatePickerExample = { to convert start and end strings into moment objects.

- {`import dateMath from '@elastic/datemath'; + {`import dateMath from '@opensearch/datemath'; const startMoment = dateMath.parse(start); // dateMath.parse is inconsistent with unparsable strings. diff --git a/src/components/date_picker/super_date_picker/date_modes.test.ts b/src/components/date_picker/super_date_picker/date_modes.test.ts index d3a2073925..343b4f79a3 100644 --- a/src/components/date_picker/super_date_picker/date_modes.test.ts +++ b/src/components/date_picker/super_date_picker/date_modes.test.ts @@ -30,9 +30,9 @@ import { getDateMode, toAbsoluteString, toRelativeString } from './date_modes'; -jest.mock('@elastic/datemath', () => { +jest.mock('@opensearch/datemath', () => { const moment = jest.requireActual('moment'); - const datemath = jest.requireActual('@elastic/datemath'); + const datemath = jest.requireActual('@opensearch/datemath'); const anchor = '2019-03-19T00:00:00.000Z'; const anchoredDate = new Date(Date.parse(anchor)); // https://momentjs.com/docs/#/customization/now/ diff --git a/src/components/date_picker/super_date_picker/date_modes.ts b/src/components/date_picker/super_date_picker/date_modes.ts index 5906b1fbb5..3e03c6b3c4 100644 --- a/src/components/date_picker/super_date_picker/date_modes.ts +++ b/src/components/date_picker/super_date_picker/date_modes.ts @@ -28,7 +28,7 @@ * under the License. */ -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import { parseRelativeParts, toRelativeStringFromParts, diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx index e7c428a408..345f2c8b44 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx @@ -32,7 +32,7 @@ import React, { Component, ChangeEventHandler } from 'react'; import moment, { Moment, LocaleSpecifier } from 'moment'; // eslint-disable-line import/named -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import { OuiDatePicker, OuiDatePickerProps } from '../../date_picker'; import { OuiFormRow, OuiFieldText, OuiFormLabel } from '../../../form'; diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx index 5f242030e2..80e3de1c2c 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx @@ -29,7 +29,7 @@ */ import React, { Component, ChangeEventHandler } from 'react'; -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import { toSentenceCase } from '../../../../services/string/to_case'; import { htmlIdGenerator } from '../../../../services'; import { OuiFlexGroup, OuiFlexItem } from '../../../flex'; diff --git a/src/components/date_picker/super_date_picker/pretty_duration.ts b/src/components/date_picker/super_date_picker/pretty_duration.ts index eb5f8c07a7..bf56bc6d55 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.ts +++ b/src/components/date_picker/super_date_picker/pretty_duration.ts @@ -28,7 +28,7 @@ * under the License. */ -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import moment, { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named import { timeUnits, timeUnitsPlural } from './time_units'; import { getDateMode, DATE_MODES } from './date_modes'; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx index 9d7b83d8e2..17e7b13d1b 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx @@ -34,7 +34,7 @@ import React, { KeyboardEventHandler, } from 'react'; import moment from 'moment'; -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import { htmlIdGenerator } from '../../../../services'; import { OuiButton, OuiButtonIcon } from '../../../button'; import { OuiFlexGroup, OuiFlexItem } from '../../../flex'; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts index c69124da14..9f5f35fc89 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts @@ -29,7 +29,7 @@ */ import moment from 'moment'; -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import { isString } from '../../../../services/predicate'; import { relativeUnitsFromLargestToSmallest } from '../relative_options'; import { DATE_MODES } from '../date_modes'; diff --git a/src/components/date_picker/super_date_picker/relative_utils.ts b/src/components/date_picker/super_date_picker/relative_utils.ts index 59b4c807e7..df471c376c 100644 --- a/src/components/date_picker/super_date_picker/relative_utils.ts +++ b/src/components/date_picker/super_date_picker/relative_utils.ts @@ -28,7 +28,7 @@ * under the License. */ -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import moment from 'moment'; import { get } from '../../../services/objects'; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.tsx b/src/components/date_picker/super_date_picker/super_date_picker.tsx index 1d301d1487..2e475e905a 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -37,7 +37,7 @@ import { } from './pretty_duration'; import { prettyInterval } from './pretty_interval'; -import dateMath from '@elastic/datemath'; +import dateMath from '@opensearch/datemath'; import { OuiSuperUpdateButton, diff --git a/wiki/consuming.md b/wiki/consuming.md index 94b3138f80..c683a4d68b 100644 --- a/wiki/consuming.md +++ b/wiki/consuming.md @@ -4,7 +4,7 @@ OUI expects that you polyfill ES2015 features, e.g. [`babel-polyfill`](https://babeljs.io/docs/usage/polyfill/). Without an ES2015 polyfill your app might throw errors on certain browsers. -OUI also has `moment` and `@elastic/datemath` as dependencies itself. These are already loaded in most OpenSearch repos, but make sure to install them if you are starting from scratch. +OUI also has `moment` as a dependency itself. This is already loaded in most OpenSearch repos, but make sure to install it if you are starting from scratch. ## What's available diff --git a/yarn.lock b/yarn.lock index d647fad2bd..d297124a59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1304,13 +1304,6 @@ utility-types "^3.10.0" uuid "^3.3.2" -"@elastic/datemath@^5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@elastic/datemath/-/datemath-5.0.3.tgz#7baccdab672b9a3ecb7fe8387580670936b58573" - integrity sha512-8Hbr1Uyjm5OcYBfEB60K7sCP6U3IXuWDaLaQmYv3UxgI4jqBWbakoemwWvsqPVUvnwEjuX6z7ghPZbefs8xiaA== - dependencies: - tslib "^1.9.3" - "@elastic/eslint-config-kibana@^0.15.0": version "0.15.0" resolved "https://registry.yarnpkg.com/@elastic/eslint-config-kibana/-/eslint-config-kibana-0.15.0.tgz#a552793497cdfc1829c2f9b7cd7018eb008f1606" @@ -1929,6 +1922,9 @@ dependencies: "@octokit/openapi-types" "^12.11.0" +"@opensearch/datemath@file:./packages/opensearch-datemath": + version "5.0.3" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"