mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[RAM] Remove third party RRule library, replace with own timezone-compliant lib (#152873)
## Summary Closes #152630 ~Adds a fix for the weird UTC-but-not-really expected inputs in rrule.js~ This PR removes the third-party `rrule` package and replaces it with `@kbn/rrule`. The third party RRule library's functions produced different results depending on what system timezone you ran it in. It would output local timestamps in UTC, making it impossible to do reliable math on them. It's now replaced with our own library that passes all of our own tests for the limited cross-section of the RRule spec that we need to support. It's possible that it wouldn't stand up to the rigor of more complex RRule queries, but it supports the ones that our Recurrence Scheduler UI supports just fine. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
dea3423b2f
commit
e31ede27de
47 changed files with 1640 additions and 285 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -552,6 +552,7 @@ examples/response_stream @elastic/ml-ui
|
|||
packages/kbn-rison @elastic/kibana-operations
|
||||
x-pack/plugins/rollup @elastic/platform-deployment-management
|
||||
examples/routing_example @elastic/kibana-core
|
||||
packages/kbn-rrule @elastic/response-ops
|
||||
packages/kbn-rule-data-utils @elastic/security-detections-response @elastic/actionable-observability @elastic/response-ops
|
||||
x-pack/plugins/rule_registry @elastic/response-ops @elastic/actionable-observability
|
||||
x-pack/plugins/runtime_fields @elastic/platform-deployment-management
|
||||
|
|
|
@ -555,6 +555,7 @@
|
|||
"@kbn/rison": "link:packages/kbn-rison",
|
||||
"@kbn/rollup-plugin": "link:x-pack/plugins/rollup",
|
||||
"@kbn/routing-example-plugin": "link:examples/routing_example",
|
||||
"@kbn/rrule": "link:packages/kbn-rrule",
|
||||
"@kbn/rule-data-utils": "link:packages/kbn-rule-data-utils",
|
||||
"@kbn/rule-registry-plugin": "link:x-pack/plugins/rule_registry",
|
||||
"@kbn/runtime-fields-plugin": "link:x-pack/plugins/runtime_fields",
|
||||
|
@ -965,7 +966,6 @@
|
|||
"require-in-the-middle": "^6.0.0",
|
||||
"reselect": "^4.1.6",
|
||||
"rison-node": "1.0.2",
|
||||
"rrule": "2.6.4",
|
||||
"rxjs": "^7.5.5",
|
||||
"safe-squel": "^5.12.5",
|
||||
"seedrandom": "^3.0.5",
|
||||
|
|
70
packages/kbn-rrule/README.md
Normal file
70
packages/kbn-rrule/README.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
# @kbn/rrule
|
||||
|
||||
A rewrite of [rrule.js](https://github.com/jakubroztocil/rrule) using `moment-timezone` for timezone support. Ensures that we always get the same outputs no matter what local timezone the executing system is set to.
|
||||
|
||||
Differences from library on Github:
|
||||
|
||||
- It is **recommended** to generate input Dates from UTC timestamps, but not required. This implementation will perform calculations on inputted Dates accurate to their corresponding Unix timestamps.
|
||||
- Timezones IDs are required. They're very important for dealing with things like day-of-week changes or DST.
|
||||
- `inc` argument from `between`, `before`, `after` is removed, and is computed as if it were `true`
|
||||
- SECONDLY frequency is not implemented.
|
||||
- This implementation may not accurately support the entire [iCalendar RRULE RFC](https://www.rfc-editor.org/rfc/rfc5545). It is known to work for common scenarios configurable in the Recurrence Scheduler UI, plus some other more complicated ones. See `rrule.test.ts` for known working configurations.
|
||||
|
||||
Known not to work are mostly edge cases:
|
||||
|
||||
- Manually configuring `setpos` with any frequency besides `MONTHLY`
|
||||
- `wkst` doesn't seem to have an effect on anything (I was also unable to get it to affect anything in the original library though)
|
||||
- Setting `byyearday` on anything besides `Frequency.YEARLY`, setting `bymonthday` on anything besides `MONTHLY`, and other similar odd situations
|
||||
|
||||
## Constructor
|
||||
|
||||
Create an RRule with the following options:
|
||||
|
||||
```ts
|
||||
new RRule({
|
||||
dtstart: Date; // Recommended to generate this from a UTC timestamp, but this impl
|
||||
tzid: string; // Takes a Moment.js timezone string. Recommended to use a country and city for DST accuracy, e.g. America/Phoenix and America/Denver are both in Mountain time but Phoenix doesn't observe DST
|
||||
freq?: Frequency; // Defaults to YEARLY
|
||||
interval?: number; // Every x freq, e.g. 1 and YEARLY is every 1 year, 2 and WEEKLY is every 2 weeks
|
||||
until?: Date; // Recur until this date
|
||||
count?: number; // Number of times this rule should recur until it stops
|
||||
wkst?: Weekday | number; // Start of week, defaults to Monday
|
||||
// The following, if not provided, will be automatically derived from the dtstart
|
||||
byweekday?: Weekday[] | string[]; // Day(s) of the week to recur, OR nth-day-of-month strings, e.g. "+2TU" second Tuesday of month, "-1FR" last Friday of the month, which will get internally converted to a byweekday/bysetpos combination
|
||||
bysetpos?: number[]; // Positive or negative integer affecting nth day of the month, eg -2 combined with byweekday of FR is 2nd to last Friday of the month. Best not to set this manually and just use byweekday.
|
||||
byyearday?: number[]; // Day(s) of the year that this rule should recur, e.g. 32 is Feb 1. Respects leap years.
|
||||
bymonth?: number[]; // Month(s) of the year that this rule should recur
|
||||
bymonthday?: number[]; // Day(s) of the momth to recur
|
||||
byhour?: number[]; // Hour(s) of the day to recur
|
||||
byminute?: number[]; // Minute(s) of the hour to recur
|
||||
bysecond?: number[]; // Seconds(s) of the day to recur
|
||||
});
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### `RRule.prototype.all(limit?: number)`
|
||||
|
||||
returns `Date[] & { hasMore?: boolean}`
|
||||
|
||||
Returns an array all dates matching the rule. By default, limited to 10000 iterations. Pass something to `limit` to change this.
|
||||
|
||||
If it hits the limit, the array will come back with the property `hasMore: true`.
|
||||
|
||||
### `RRule.prototype.before(dt: Date)`
|
||||
|
||||
returns `Date | null`
|
||||
|
||||
Returns the last recurrence before `dt`, or `null` if there is none.
|
||||
|
||||
### RRule.prototype.after(dt: Date)`
|
||||
|
||||
returns `Date | null`
|
||||
|
||||
Returns the last recurrence after `dt`, or `null` if there is none.
|
||||
|
||||
### RRule.prototype.between(start: Date, end: Date)`
|
||||
|
||||
returns `Date[]`
|
||||
|
||||
Returns an array of all dates between `start` and `end`.
|
11
packages/kbn-rrule/index.ts
Normal file
11
packages/kbn-rrule/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { RRule, Frequency, Weekday } from './rrule';
|
||||
export type { Options } from './rrule';
|
||||
export declare type WeekdayStr = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU';
|
13
packages/kbn-rrule/jest.config.js
Normal file
13
packages/kbn-rrule/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-rrule'],
|
||||
};
|
5
packages/kbn-rrule/kibana.jsonc
Normal file
5
packages/kbn-rrule/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/rrule",
|
||||
"owner": "@elastic/response-ops"
|
||||
}
|
6
packages/kbn-rrule/package.json
Normal file
6
packages/kbn-rrule/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/rrule",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
874
packages/kbn-rrule/rrule.test.ts
Normal file
874
packages/kbn-rrule/rrule.test.ts
Normal file
|
@ -0,0 +1,874 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import sinon from 'sinon';
|
||||
import { RRule, Frequency, Weekday } from './rrule';
|
||||
|
||||
const DATE_2019 = '2019-01-01T00:00:00.000Z';
|
||||
const DATE_2019_DECEMBER_19 = '2019-12-19T00:00:00.000Z';
|
||||
const DATE_2019_FEB_28 = '2019-02-28T00:00:00.000Z';
|
||||
const DATE_2020 = '2020-01-01T00:00:00.000Z';
|
||||
const DATE_2020_MINUS_1_MONTH = '2019-12-01T00:00:00.000Z';
|
||||
const DATE_2020_FEB_28 = '2020-02-28T00:00:00.000Z';
|
||||
const DATE_2023 = '2023-01-01T00:00:00.000Z';
|
||||
const DATE_2023_JAN_6_11PM = '2023-01-06T23:00:00Z';
|
||||
|
||||
const INVALID_DATE = '2020-01-01-01-01T:00:00:00Z';
|
||||
|
||||
const NOW = DATE_2020;
|
||||
|
||||
let fakeTimer: sinon.SinonFakeTimers;
|
||||
|
||||
describe('RRule', () => {
|
||||
beforeAll(() => {
|
||||
fakeTimer = sinon.useFakeTimers(new Date(NOW));
|
||||
});
|
||||
|
||||
afterAll(() => fakeTimer.restore());
|
||||
|
||||
describe('frequency', () => {
|
||||
it('works with yearly', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule.all(10)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2020-01-01T00:00:00.000Z,
|
||||
2021-01-01T00:00:00.000Z,
|
||||
2022-01-01T00:00:00.000Z,
|
||||
2023-01-01T00:00:00.000Z,
|
||||
2024-01-01T00:00:00.000Z,
|
||||
2025-01-01T00:00:00.000Z,
|
||||
2026-01-01T00:00:00.000Z,
|
||||
2027-01-01T00:00:00.000Z,
|
||||
2028-01-01T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2020),
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 3,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule2.all(10)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2020-01-01T00:00:00.000Z,
|
||||
2023-01-01T00:00:00.000Z,
|
||||
2026-01-01T00:00:00.000Z,
|
||||
2029-01-01T00:00:00.000Z,
|
||||
2032-01-01T00:00:00.000Z,
|
||||
2035-01-01T00:00:00.000Z,
|
||||
2038-01-01T00:00:00.000Z,
|
||||
2041-01-01T00:00:00.000Z,
|
||||
2044-01-01T00:00:00.000Z,
|
||||
2047-01-01T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with monthly', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule.all(15)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-02-01T00:00:00.000Z,
|
||||
2019-03-01T00:00:00.000Z,
|
||||
2019-04-01T00:00:00.000Z,
|
||||
2019-05-01T00:00:00.000Z,
|
||||
2019-06-01T00:00:00.000Z,
|
||||
2019-07-01T00:00:00.000Z,
|
||||
2019-08-01T00:00:00.000Z,
|
||||
2019-09-01T00:00:00.000Z,
|
||||
2019-10-01T00:00:00.000Z,
|
||||
2019-11-01T00:00:00.000Z,
|
||||
2019-12-01T00:00:00.000Z,
|
||||
2020-01-01T00:00:00.000Z,
|
||||
2020-02-01T00:00:00.000Z,
|
||||
2020-03-01T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 6,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule2.all(6)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-07-01T00:00:00.000Z,
|
||||
2020-01-01T00:00:00.000Z,
|
||||
2020-07-01T00:00:00.000Z,
|
||||
2021-01-01T00:00:00.000Z,
|
||||
2021-07-01T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule3 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
bymonthday: [10, 20],
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 6,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule3.all(6)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-10T00:00:00.000Z,
|
||||
2019-01-20T00:00:00.000Z,
|
||||
2019-07-10T00:00:00.000Z,
|
||||
2019-07-20T00:00:00.000Z,
|
||||
2020-01-10T00:00:00.000Z,
|
||||
2020-01-20T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with weekly', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2019-12-26T00:00:00.000Z,
|
||||
2020-01-02T00:00:00.000Z,
|
||||
2020-01-09T00:00:00.000Z,
|
||||
2020-01-16T00:00:00.000Z,
|
||||
2020-01-23T00:00:00.000Z,
|
||||
2020-01-30T00:00:00.000Z,
|
||||
2020-02-06T00:00:00.000Z,
|
||||
2020-02-13T00:00:00.000Z,
|
||||
2020-02-20T00:00:00.000Z,
|
||||
2020-02-27T00:00:00.000Z,
|
||||
2020-03-05T00:00:00.000Z,
|
||||
2020-03-12T00:00:00.000Z,
|
||||
2020-03-19T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 2,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule2.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2020-01-02T00:00:00.000Z,
|
||||
2020-01-16T00:00:00.000Z,
|
||||
2020-01-30T00:00:00.000Z,
|
||||
2020-02-13T00:00:00.000Z,
|
||||
2020-02-27T00:00:00.000Z,
|
||||
2020-03-12T00:00:00.000Z,
|
||||
2020-03-26T00:00:00.000Z,
|
||||
2020-04-09T00:00:00.000Z,
|
||||
2020-04-23T00:00:00.000Z,
|
||||
2020-05-07T00:00:00.000Z,
|
||||
2020-05-21T00:00:00.000Z,
|
||||
2020-06-04T00:00:00.000Z,
|
||||
2020-06-18T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with daily', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule.all(30)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2019-12-20T00:00:00.000Z,
|
||||
2019-12-21T00:00:00.000Z,
|
||||
2019-12-22T00:00:00.000Z,
|
||||
2019-12-23T00:00:00.000Z,
|
||||
2019-12-24T00:00:00.000Z,
|
||||
2019-12-25T00:00:00.000Z,
|
||||
2019-12-26T00:00:00.000Z,
|
||||
2019-12-27T00:00:00.000Z,
|
||||
2019-12-28T00:00:00.000Z,
|
||||
2019-12-29T00:00:00.000Z,
|
||||
2019-12-30T00:00:00.000Z,
|
||||
2019-12-31T00:00:00.000Z,
|
||||
2020-01-01T00:00:00.000Z,
|
||||
2020-01-02T00:00:00.000Z,
|
||||
2020-01-03T00:00:00.000Z,
|
||||
2020-01-04T00:00:00.000Z,
|
||||
2020-01-05T00:00:00.000Z,
|
||||
2020-01-06T00:00:00.000Z,
|
||||
2020-01-07T00:00:00.000Z,
|
||||
2020-01-08T00:00:00.000Z,
|
||||
2020-01-09T00:00:00.000Z,
|
||||
2020-01-10T00:00:00.000Z,
|
||||
2020-01-11T00:00:00.000Z,
|
||||
2020-01-12T00:00:00.000Z,
|
||||
2020-01-13T00:00:00.000Z,
|
||||
2020-01-14T00:00:00.000Z,
|
||||
2020-01-15T00:00:00.000Z,
|
||||
2020-01-16T00:00:00.000Z,
|
||||
2020-01-17T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 48,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule2.all(12)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2020-02-05T00:00:00.000Z,
|
||||
2020-03-24T00:00:00.000Z,
|
||||
2020-05-11T00:00:00.000Z,
|
||||
2020-06-28T00:00:00.000Z,
|
||||
2020-08-15T00:00:00.000Z,
|
||||
2020-10-02T00:00:00.000Z,
|
||||
2020-11-19T00:00:00.000Z,
|
||||
2021-01-06T00:00:00.000Z,
|
||||
2021-02-23T00:00:00.000Z,
|
||||
2021-04-12T00:00:00.000Z,
|
||||
2021-05-30T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule3 = new RRule({
|
||||
dtstart: new Date(DATE_2019_FEB_28),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule3.all(6)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-02-28T00:00:00.000Z,
|
||||
2019-03-01T00:00:00.000Z,
|
||||
2019-03-02T00:00:00.000Z,
|
||||
2019-03-03T00:00:00.000Z,
|
||||
2019-03-04T00:00:00.000Z,
|
||||
2019-03-05T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule4 = new RRule({
|
||||
dtstart: new Date(DATE_2020_FEB_28),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule4.all(6)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2020-02-28T00:00:00.000Z,
|
||||
2020-02-29T00:00:00.000Z,
|
||||
2020-03-01T00:00:00.000Z,
|
||||
2020-03-02T00:00:00.000Z,
|
||||
2020-03-03T00:00:00.000Z,
|
||||
2020-03-04T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with hourly', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule.all(30)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-01-01T01:00:00.000Z,
|
||||
2019-01-01T02:00:00.000Z,
|
||||
2019-01-01T03:00:00.000Z,
|
||||
2019-01-01T04:00:00.000Z,
|
||||
2019-01-01T05:00:00.000Z,
|
||||
2019-01-01T06:00:00.000Z,
|
||||
2019-01-01T07:00:00.000Z,
|
||||
2019-01-01T08:00:00.000Z,
|
||||
2019-01-01T09:00:00.000Z,
|
||||
2019-01-01T10:00:00.000Z,
|
||||
2019-01-01T11:00:00.000Z,
|
||||
2019-01-01T12:00:00.000Z,
|
||||
2019-01-01T13:00:00.000Z,
|
||||
2019-01-01T14:00:00.000Z,
|
||||
2019-01-01T15:00:00.000Z,
|
||||
2019-01-01T16:00:00.000Z,
|
||||
2019-01-01T17:00:00.000Z,
|
||||
2019-01-01T18:00:00.000Z,
|
||||
2019-01-01T19:00:00.000Z,
|
||||
2019-01-01T20:00:00.000Z,
|
||||
2019-01-01T21:00:00.000Z,
|
||||
2019-01-01T22:00:00.000Z,
|
||||
2019-01-01T23:00:00.000Z,
|
||||
2019-01-02T00:00:00.000Z,
|
||||
2019-01-02T01:00:00.000Z,
|
||||
2019-01-02T02:00:00.000Z,
|
||||
2019-01-02T03:00:00.000Z,
|
||||
2019-01-02T04:00:00.000Z,
|
||||
2019-01-02T05:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 36,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule2.all(30)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-01-02T12:00:00.000Z,
|
||||
2019-01-04T00:00:00.000Z,
|
||||
2019-01-05T12:00:00.000Z,
|
||||
2019-01-07T00:00:00.000Z,
|
||||
2019-01-08T12:00:00.000Z,
|
||||
2019-01-10T00:00:00.000Z,
|
||||
2019-01-11T12:00:00.000Z,
|
||||
2019-01-13T00:00:00.000Z,
|
||||
2019-01-14T12:00:00.000Z,
|
||||
2019-01-16T00:00:00.000Z,
|
||||
2019-01-17T12:00:00.000Z,
|
||||
2019-01-19T00:00:00.000Z,
|
||||
2019-01-20T12:00:00.000Z,
|
||||
2019-01-22T00:00:00.000Z,
|
||||
2019-01-23T12:00:00.000Z,
|
||||
2019-01-25T00:00:00.000Z,
|
||||
2019-01-26T12:00:00.000Z,
|
||||
2019-01-28T00:00:00.000Z,
|
||||
2019-01-29T12:00:00.000Z,
|
||||
2019-01-31T00:00:00.000Z,
|
||||
2019-02-01T12:00:00.000Z,
|
||||
2019-02-03T00:00:00.000Z,
|
||||
2019-02-04T12:00:00.000Z,
|
||||
2019-02-06T00:00:00.000Z,
|
||||
2019-02-07T12:00:00.000Z,
|
||||
2019-02-09T00:00:00.000Z,
|
||||
2019-02-10T12:00:00.000Z,
|
||||
2019-02-12T00:00:00.000Z,
|
||||
2019-02-13T12:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with minutely', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.MINUTELY,
|
||||
interval: 15,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule.all(10)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-01-01T00:15:00.000Z,
|
||||
2019-01-01T00:30:00.000Z,
|
||||
2019-01-01T00:45:00.000Z,
|
||||
2019-01-01T01:00:00.000Z,
|
||||
2019-01-01T01:15:00.000Z,
|
||||
2019-01-01T01:30:00.000Z,
|
||||
2019-01-01T01:45:00.000Z,
|
||||
2019-01-01T02:00:00.000Z,
|
||||
2019-01-01T02:15:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.MINUTELY,
|
||||
interval: 36,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule2.all(30)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-01-01T00:36:00.000Z,
|
||||
2019-01-01T01:12:00.000Z,
|
||||
2019-01-01T01:48:00.000Z,
|
||||
2019-01-01T02:24:00.000Z,
|
||||
2019-01-01T03:00:00.000Z,
|
||||
2019-01-01T03:36:00.000Z,
|
||||
2019-01-01T04:12:00.000Z,
|
||||
2019-01-01T04:48:00.000Z,
|
||||
2019-01-01T05:24:00.000Z,
|
||||
2019-01-01T06:00:00.000Z,
|
||||
2019-01-01T06:36:00.000Z,
|
||||
2019-01-01T07:12:00.000Z,
|
||||
2019-01-01T07:48:00.000Z,
|
||||
2019-01-01T08:24:00.000Z,
|
||||
2019-01-01T09:00:00.000Z,
|
||||
2019-01-01T09:36:00.000Z,
|
||||
2019-01-01T10:12:00.000Z,
|
||||
2019-01-01T10:48:00.000Z,
|
||||
2019-01-01T11:24:00.000Z,
|
||||
2019-01-01T12:00:00.000Z,
|
||||
2019-01-01T12:36:00.000Z,
|
||||
2019-01-01T13:12:00.000Z,
|
||||
2019-01-01T13:48:00.000Z,
|
||||
2019-01-01T14:24:00.000Z,
|
||||
2019-01-01T15:00:00.000Z,
|
||||
2019-01-01T15:36:00.000Z,
|
||||
2019-01-01T16:12:00.000Z,
|
||||
2019-01-01T16:48:00.000Z,
|
||||
2019-01-01T17:24:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
it('works with until', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
until: new Date(DATE_2020_MINUS_1_MONTH),
|
||||
});
|
||||
expect(rule.all().length).toBe(12);
|
||||
});
|
||||
|
||||
it('works with count', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 20,
|
||||
});
|
||||
expect(rule.all()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-01T00:00:00.000Z,
|
||||
2019-02-01T00:00:00.000Z,
|
||||
2019-03-01T00:00:00.000Z,
|
||||
2019-04-01T00:00:00.000Z,
|
||||
2019-05-01T00:00:00.000Z,
|
||||
2019-06-01T00:00:00.000Z,
|
||||
2019-07-01T00:00:00.000Z,
|
||||
2019-08-01T00:00:00.000Z,
|
||||
2019-09-01T00:00:00.000Z,
|
||||
2019-10-01T00:00:00.000Z,
|
||||
2019-11-01T00:00:00.000Z,
|
||||
2019-12-01T00:00:00.000Z,
|
||||
2020-01-01T00:00:00.000Z,
|
||||
2020-02-01T00:00:00.000Z,
|
||||
2020-03-01T00:00:00.000Z,
|
||||
2020-04-01T00:00:00.000Z,
|
||||
2020-05-01T00:00:00.000Z,
|
||||
2020-06-01T00:00:00.000Z,
|
||||
2020-07-01T00:00:00.000Z,
|
||||
2020-08-01T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
describe('byweekday', () => {
|
||||
it('works with weekly frequency', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.TH],
|
||||
});
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2019-12-26T00:00:00.000Z,
|
||||
2020-01-02T00:00:00.000Z,
|
||||
2020-01-09T00:00:00.000Z,
|
||||
2020-01-16T00:00:00.000Z,
|
||||
2020-01-23T00:00:00.000Z,
|
||||
2020-01-30T00:00:00.000Z,
|
||||
2020-02-06T00:00:00.000Z,
|
||||
2020-02-13T00:00:00.000Z,
|
||||
2020-02-20T00:00:00.000Z,
|
||||
2020-02-27T00:00:00.000Z,
|
||||
2020-03-05T00:00:00.000Z,
|
||||
2020-03-12T00:00:00.000Z,
|
||||
2020-03-19T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.SA, Weekday.SU, Weekday.MO],
|
||||
});
|
||||
|
||||
expect(rule2.all(9)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-05T00:00:00.000Z,
|
||||
2019-01-06T00:00:00.000Z,
|
||||
2019-01-07T00:00:00.000Z,
|
||||
2019-01-12T00:00:00.000Z,
|
||||
2019-01-13T00:00:00.000Z,
|
||||
2019-01-14T00:00:00.000Z,
|
||||
2019-01-19T00:00:00.000Z,
|
||||
2019-01-20T00:00:00.000Z,
|
||||
2019-01-21T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with daily frequency by behaving like weekly frequency', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.TH],
|
||||
});
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2019-12-26T00:00:00.000Z,
|
||||
2020-01-02T00:00:00.000Z,
|
||||
2020-01-09T00:00:00.000Z,
|
||||
2020-01-16T00:00:00.000Z,
|
||||
2020-01-23T00:00:00.000Z,
|
||||
2020-01-30T00:00:00.000Z,
|
||||
2020-02-06T00:00:00.000Z,
|
||||
2020-02-13T00:00:00.000Z,
|
||||
2020-02-20T00:00:00.000Z,
|
||||
2020-02-27T00:00:00.000Z,
|
||||
2020-03-05T00:00:00.000Z,
|
||||
2020-03-12T00:00:00.000Z,
|
||||
2020-03-19T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.SA, Weekday.SU, Weekday.MO],
|
||||
});
|
||||
|
||||
expect(rule2.all(9)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-05T00:00:00.000Z,
|
||||
2019-01-06T00:00:00.000Z,
|
||||
2019-01-07T00:00:00.000Z,
|
||||
2019-01-12T00:00:00.000Z,
|
||||
2019-01-13T00:00:00.000Z,
|
||||
2019-01-14T00:00:00.000Z,
|
||||
2019-01-19T00:00:00.000Z,
|
||||
2019-01-20T00:00:00.000Z,
|
||||
2019-01-21T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with monthly frequency with non-setpos syntax by behaving like weekly frequency', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.TH],
|
||||
});
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:00.000Z,
|
||||
2019-12-26T00:00:00.000Z,
|
||||
2020-01-02T00:00:00.000Z,
|
||||
2020-01-09T00:00:00.000Z,
|
||||
2020-01-16T00:00:00.000Z,
|
||||
2020-01-23T00:00:00.000Z,
|
||||
2020-01-30T00:00:00.000Z,
|
||||
2020-02-06T00:00:00.000Z,
|
||||
2020-02-13T00:00:00.000Z,
|
||||
2020-02-20T00:00:00.000Z,
|
||||
2020-02-27T00:00:00.000Z,
|
||||
2020-03-05T00:00:00.000Z,
|
||||
2020-03-12T00:00:00.000Z,
|
||||
2020-03-19T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2019),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.SA, Weekday.SU, Weekday.MO],
|
||||
});
|
||||
|
||||
expect(rule2.all(9)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-01-05T00:00:00.000Z,
|
||||
2019-01-06T00:00:00.000Z,
|
||||
2019-01-07T00:00:00.000Z,
|
||||
2019-01-12T00:00:00.000Z,
|
||||
2019-01-13T00:00:00.000Z,
|
||||
2019-01-14T00:00:00.000Z,
|
||||
2019-01-19T00:00:00.000Z,
|
||||
2019-01-20T00:00:00.000Z,
|
||||
2019-01-21T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with monthly frequency using setpos syntax', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2023),
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['+1TU', '+2TU', '-1FR', '-2FR'],
|
||||
});
|
||||
const result = rule.all(12);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2023-01-03T00:00:00.000Z,
|
||||
2023-01-10T00:00:00.000Z,
|
||||
2023-01-20T00:00:00.000Z,
|
||||
2023-01-27T00:00:00.000Z,
|
||||
2023-02-07T00:00:00.000Z,
|
||||
2023-02-14T00:00:00.000Z,
|
||||
2023-02-17T00:00:00.000Z,
|
||||
2023-02-24T00:00:00.000Z,
|
||||
2023-03-07T00:00:00.000Z,
|
||||
2023-03-14T00:00:00.000Z,
|
||||
2023-03-24T00:00:00.000Z,
|
||||
2023-03-31T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('works with timezones', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2023_JAN_6_11PM),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'Europe/Madrid',
|
||||
byweekday: [Weekday.SA],
|
||||
});
|
||||
expect(rule.all(12)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2023-01-06T23:00:00.000Z,
|
||||
2023-01-13T23:00:00.000Z,
|
||||
2023-01-20T23:00:00.000Z,
|
||||
2023-01-27T23:00:00.000Z,
|
||||
2023-02-03T23:00:00.000Z,
|
||||
2023-02-10T23:00:00.000Z,
|
||||
2023-02-17T23:00:00.000Z,
|
||||
2023-02-24T23:00:00.000Z,
|
||||
2023-03-03T23:00:00.000Z,
|
||||
2023-03-10T23:00:00.000Z,
|
||||
2023-03-17T23:00:00.000Z,
|
||||
2023-03-24T23:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
|
||||
const rule2 = new RRule({
|
||||
dtstart: new Date(DATE_2023_JAN_6_11PM),
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: [Weekday.SA],
|
||||
});
|
||||
|
||||
expect(rule2.all(12)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2023-01-07T23:00:00.000Z,
|
||||
2023-01-14T23:00:00.000Z,
|
||||
2023-01-21T23:00:00.000Z,
|
||||
2023-01-28T23:00:00.000Z,
|
||||
2023-02-04T23:00:00.000Z,
|
||||
2023-02-11T23:00:00.000Z,
|
||||
2023-02-18T23:00:00.000Z,
|
||||
2023-02-25T23:00:00.000Z,
|
||||
2023-03-04T23:00:00.000Z,
|
||||
2023-03-11T23:00:00.000Z,
|
||||
2023-03-18T23:00:00.000Z,
|
||||
2023-03-25T23:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('byhour, byminute, bysecond', () => {
|
||||
it('works with daily frequency', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byhour: [14],
|
||||
byminute: [30],
|
||||
bysecond: [0, 15],
|
||||
});
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T14:30:00.000Z,
|
||||
2019-12-19T14:30:15.000Z,
|
||||
2019-12-20T14:30:00.000Z,
|
||||
2019-12-20T14:30:15.000Z,
|
||||
2019-12-21T14:30:00.000Z,
|
||||
2019-12-21T14:30:15.000Z,
|
||||
2019-12-22T14:30:00.000Z,
|
||||
2019-12-22T14:30:15.000Z,
|
||||
2019-12-23T14:30:00.000Z,
|
||||
2019-12-23T14:30:15.000Z,
|
||||
2019-12-24T14:30:00.000Z,
|
||||
2019-12-24T14:30:15.000Z,
|
||||
2019-12-25T14:30:00.000Z,
|
||||
2019-12-25T14:30:15.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
it('works with hourly frequency', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byminute: [15, 30],
|
||||
bysecond: [30, 0],
|
||||
});
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:15:30.000Z,
|
||||
2019-12-19T00:15:00.000Z,
|
||||
2019-12-19T00:30:30.000Z,
|
||||
2019-12-19T00:30:00.000Z,
|
||||
2019-12-19T01:15:30.000Z,
|
||||
2019-12-19T01:15:00.000Z,
|
||||
2019-12-19T01:30:30.000Z,
|
||||
2019-12-19T01:30:00.000Z,
|
||||
2019-12-19T02:15:30.000Z,
|
||||
2019-12-19T02:15:00.000Z,
|
||||
2019-12-19T02:30:30.000Z,
|
||||
2019-12-19T02:30:00.000Z,
|
||||
2019-12-19T03:15:30.000Z,
|
||||
2019-12-19T03:15:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
it('works with minutely frequency', () => {
|
||||
const rule = new RRule({
|
||||
dtstart: new Date(DATE_2019_DECEMBER_19),
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
bysecond: [10, 30, 58],
|
||||
});
|
||||
expect(rule.all(14)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2019-12-19T00:00:10.000Z,
|
||||
2019-12-19T00:00:30.000Z,
|
||||
2019-12-19T00:00:58.000Z,
|
||||
2019-12-19T00:01:10.000Z,
|
||||
2019-12-19T00:01:30.000Z,
|
||||
2019-12-19T00:01:58.000Z,
|
||||
2019-12-19T00:02:10.000Z,
|
||||
2019-12-19T00:02:30.000Z,
|
||||
2019-12-19T00:02:58.000Z,
|
||||
2019-12-19T00:03:10.000Z,
|
||||
2019-12-19T00:03:30.000Z,
|
||||
2019-12-19T00:03:58.000Z,
|
||||
2019-12-19T00:04:10.000Z,
|
||||
2019-12-19T00:04:30.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('byyearday', () => {
|
||||
it('respects leap years', () => {
|
||||
const rule3 = new RRule({
|
||||
dtstart: new Date(DATE_2020),
|
||||
freq: Frequency.YEARLY,
|
||||
byyearday: [92],
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
|
||||
expect(rule3.all(10)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
2020-04-01T00:00:00.000Z,
|
||||
2021-04-02T00:00:00.000Z,
|
||||
2022-04-02T00:00:00.000Z,
|
||||
2023-04-02T00:00:00.000Z,
|
||||
2024-04-01T00:00:00.000Z,
|
||||
2025-04-02T00:00:00.000Z,
|
||||
2026-04-02T00:00:00.000Z,
|
||||
2027-04-02T00:00:00.000Z,
|
||||
2028-04-01T00:00:00.000Z,
|
||||
2029-04-02T00:00:00.000Z,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('throws an error on an invalid dtstart', () => {
|
||||
const testFn = () =>
|
||||
new RRule({
|
||||
dtstart: new Date(INVALID_DATE),
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
expect(testFn).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Cannot create RRule: dtstart is an invalid date"`
|
||||
);
|
||||
});
|
||||
it('throws an error on an invalid until', () => {
|
||||
const testFn = () =>
|
||||
new RRule({
|
||||
dtstart: new Date(DATE_2020),
|
||||
until: new Date(INVALID_DATE),
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
});
|
||||
expect(testFn).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Cannot create RRule: until is an invalid date"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
432
packages/kbn-rrule/rrule.ts
Normal file
432
packages/kbn-rrule/rrule.ts
Normal file
|
@ -0,0 +1,432 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import moment, { Moment } from 'moment-timezone';
|
||||
|
||||
export enum Frequency {
|
||||
YEARLY = 0,
|
||||
MONTHLY = 1,
|
||||
WEEKLY = 2,
|
||||
DAILY = 3,
|
||||
HOURLY = 4,
|
||||
MINUTELY = 5,
|
||||
}
|
||||
|
||||
export enum Weekday {
|
||||
MO = 1,
|
||||
TU = 2,
|
||||
WE = 3,
|
||||
TH = 4,
|
||||
FR = 5,
|
||||
SA = 6,
|
||||
SU = 7,
|
||||
}
|
||||
|
||||
export type WeekdayStr = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU';
|
||||
interface IterOptions {
|
||||
refDT: Moment;
|
||||
wkst?: Weekday | number | null;
|
||||
byyearday?: number[] | null;
|
||||
bymonth?: number[] | null;
|
||||
bysetpos?: number[] | null;
|
||||
bymonthday?: number[] | null;
|
||||
byweekday?: Weekday[] | null;
|
||||
byhour?: number[] | null;
|
||||
byminute?: number[] | null;
|
||||
bysecond?: number[] | null;
|
||||
}
|
||||
|
||||
type Options = Omit<IterOptions, 'refDT'> & {
|
||||
dtstart: Date;
|
||||
freq?: Frequency;
|
||||
interval?: number;
|
||||
until?: Date | null;
|
||||
count?: number;
|
||||
tzid: string;
|
||||
};
|
||||
|
||||
type ConstructorOptions = Omit<Options, 'byweekday' | 'wkst'> & {
|
||||
byweekday?: Array<string | number> | null;
|
||||
wkst?: Weekday | WeekdayStr | number | null;
|
||||
};
|
||||
|
||||
export type { ConstructorOptions as Options };
|
||||
|
||||
const ISO_WEEKDAYS = [
|
||||
Weekday.MO,
|
||||
Weekday.TU,
|
||||
Weekday.WE,
|
||||
Weekday.TH,
|
||||
Weekday.FR,
|
||||
Weekday.SA,
|
||||
Weekday.SU,
|
||||
];
|
||||
|
||||
type AllResult = Date[] & {
|
||||
hasMore?: boolean;
|
||||
};
|
||||
|
||||
const ALL_LIMIT = 10000;
|
||||
|
||||
export class RRule {
|
||||
private options: Options;
|
||||
constructor(options: ConstructorOptions) {
|
||||
this.options = options as Options;
|
||||
if (isNaN(options.dtstart.getTime())) {
|
||||
throw new Error('Cannot create RRule: dtstart is an invalid date');
|
||||
}
|
||||
if (options.until && isNaN(options.until.getTime())) {
|
||||
throw new Error('Cannot create RRule: until is an invalid date');
|
||||
}
|
||||
if (typeof options.wkst === 'string') {
|
||||
this.options.wkst = Weekday[options.wkst];
|
||||
}
|
||||
const weekdayParseResult = parseByWeekdayPos(options.byweekday);
|
||||
if (weekdayParseResult) {
|
||||
this.options.byweekday = weekdayParseResult[0];
|
||||
this.options.bysetpos = weekdayParseResult[1];
|
||||
}
|
||||
}
|
||||
|
||||
private *dateset(start?: Date, end?: Date): Generator<Date, null> {
|
||||
const isAfterDtStart = (current: Date) => current.getTime() >= this.options.dtstart.getTime();
|
||||
const isInBounds = (current: Date) => {
|
||||
const afterStart = !start || current.getTime() >= start.getTime();
|
||||
const beforeEnd = !end || current.getTime() <= end.getTime();
|
||||
|
||||
return afterStart && beforeEnd;
|
||||
};
|
||||
|
||||
const { dtstart, tzid, count, until } = this.options;
|
||||
let isFirstIteration = true;
|
||||
let yieldedRecurrenceCount = 0;
|
||||
let current: Date = moment(dtstart ?? new Date())
|
||||
.tz(tzid)
|
||||
.toDate();
|
||||
|
||||
const nextRecurrences: Moment[] = [];
|
||||
|
||||
while (
|
||||
(!count && !until) ||
|
||||
(count && yieldedRecurrenceCount < count) ||
|
||||
(until && current.getTime() < new Date(until).getTime())
|
||||
) {
|
||||
const next = nextRecurrences.shift()?.toDate();
|
||||
if (next) {
|
||||
current = next;
|
||||
if (!isAfterDtStart(current)) continue;
|
||||
yieldedRecurrenceCount++;
|
||||
if (isInBounds(current)) {
|
||||
yield current;
|
||||
} else if (start && current.getTime() > start.getTime()) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
getNextRecurrences({
|
||||
refDT: moment(current).tz(tzid),
|
||||
...this.options,
|
||||
interval: isFirstIteration ? 0 : this.options.interval,
|
||||
wkst: this.options.wkst ? (this.options.wkst as Weekday) : Weekday.MO,
|
||||
}).forEach((r) => nextRecurrences.push(r));
|
||||
isFirstIteration = false;
|
||||
if (nextRecurrences.length === 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
between(start: Date, end: Date) {
|
||||
const dates = this.dateset(start, end);
|
||||
return [...dates];
|
||||
}
|
||||
|
||||
before(dt: Date) {
|
||||
const dates = [...this.dateset(this.options.dtstart, dt)];
|
||||
return dates[dates.length - 1];
|
||||
}
|
||||
|
||||
after(dt: Date) {
|
||||
const dates = this.dateset(dt);
|
||||
return dates.next().value;
|
||||
}
|
||||
|
||||
all(limit: number = ALL_LIMIT): AllResult {
|
||||
const dateGenerator = this.dateset();
|
||||
const dates: AllResult = [];
|
||||
let next = dateGenerator.next();
|
||||
for (let i = 0; i < limit; i++) {
|
||||
if (!next.done) dates.push(next.value);
|
||||
else break;
|
||||
next = dateGenerator.next();
|
||||
}
|
||||
if (next.done) return dates;
|
||||
else {
|
||||
dates.hasMore = true;
|
||||
return dates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parseByWeekdayPos = function (byweekday: ConstructorOptions['byweekday']) {
|
||||
if (byweekday?.some((d) => typeof d === 'string')) {
|
||||
const pos: number[] = [];
|
||||
const newByweekday = byweekday.map((d) => {
|
||||
if (typeof d !== 'string') return d;
|
||||
if (Object.keys(Weekday).includes(d)) return Weekday[d as WeekdayStr];
|
||||
const [sign, number, ...rest] = d.split('');
|
||||
if (sign === '-') pos.push(-Number(number));
|
||||
else pos.push(Number(number));
|
||||
return Weekday[rest.join('') as WeekdayStr];
|
||||
});
|
||||
return [newByweekday, pos];
|
||||
} else return null;
|
||||
};
|
||||
|
||||
export const getNextRecurrences = function ({
|
||||
refDT,
|
||||
wkst = Weekday.MO,
|
||||
byyearday,
|
||||
bymonth,
|
||||
bymonthday,
|
||||
byweekday,
|
||||
byhour,
|
||||
byminute,
|
||||
bysecond,
|
||||
bysetpos,
|
||||
freq = Frequency.YEARLY,
|
||||
interval = 1,
|
||||
}: IterOptions & {
|
||||
freq?: Frequency;
|
||||
interval?: number;
|
||||
}) {
|
||||
const opts = {
|
||||
wkst,
|
||||
byyearday,
|
||||
bymonth,
|
||||
bymonthday,
|
||||
byweekday,
|
||||
byhour,
|
||||
byminute,
|
||||
bysecond,
|
||||
bysetpos,
|
||||
};
|
||||
|
||||
// If the frequency is DAILY but there's a byweekday, or if the frequency is MONTHLY with a byweekday with no
|
||||
// corresponding bysetpos, use the WEEKLY code path to determine recurrences
|
||||
const derivedFreq =
|
||||
byweekday && (freq === Frequency.DAILY || (freq === Frequency.MONTHLY && !bysetpos?.length))
|
||||
? Frequency.WEEKLY
|
||||
: freq;
|
||||
|
||||
switch (derivedFreq) {
|
||||
case Frequency.YEARLY: {
|
||||
const nextRef = moment(refDT).add(interval, 'y');
|
||||
return getYearOfRecurrences({
|
||||
refDT: nextRef,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
case Frequency.MONTHLY: {
|
||||
const nextRef = moment(refDT).add(interval, 'M');
|
||||
return getMonthOfRecurrences({
|
||||
refDT: nextRef,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
case Frequency.WEEKLY: {
|
||||
const nextRef = moment(refDT).add(interval, 'w');
|
||||
return getWeekOfRecurrences({
|
||||
refDT: nextRef,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
case Frequency.DAILY: {
|
||||
const nextRef = moment(refDT).add(interval, 'd');
|
||||
return getDayOfRecurrences({
|
||||
refDT: nextRef,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
case Frequency.HOURLY: {
|
||||
const nextRef = moment(refDT).add(interval, 'h');
|
||||
return getHourOfRecurrences({
|
||||
refDT: nextRef,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
case Frequency.MINUTELY: {
|
||||
const nextRef = moment(refDT).add(interval, 'm');
|
||||
return getMinuteOfRecurrences({
|
||||
refDT: nextRef,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sortByweekday = function ({
|
||||
wkst,
|
||||
byweekday,
|
||||
}: {
|
||||
wkst?: Weekday | null;
|
||||
byweekday: Weekday[];
|
||||
}) {
|
||||
const weekStart = wkst ?? Weekday.MO;
|
||||
const weekdays = ISO_WEEKDAYS.slice(weekStart - 1).concat(ISO_WEEKDAYS.slice(0, weekStart - 1));
|
||||
return [...byweekday].sort((a, b) => weekdays.indexOf(a) - weekdays.indexOf(b));
|
||||
};
|
||||
|
||||
const getYearOfRecurrences = function ({
|
||||
refDT,
|
||||
wkst,
|
||||
byyearday,
|
||||
bymonth,
|
||||
bymonthday,
|
||||
byweekday,
|
||||
byhour,
|
||||
byminute,
|
||||
bysecond,
|
||||
bysetpos,
|
||||
}: IterOptions) {
|
||||
const derivedByweekday = byweekday ?? ISO_WEEKDAYS;
|
||||
|
||||
if (bymonth) {
|
||||
return bymonth.flatMap((month) => {
|
||||
const currentMonth = moment(refDT).month(month - 1);
|
||||
return getMonthOfRecurrences({
|
||||
refDT: currentMonth,
|
||||
wkst,
|
||||
bymonthday,
|
||||
byweekday,
|
||||
byhour,
|
||||
byminute,
|
||||
bysecond,
|
||||
bysetpos,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const derivedByyearday = byyearday ?? [refDT.dayOfYear()];
|
||||
|
||||
return derivedByyearday.flatMap((dayOfYear) => {
|
||||
const currentDate = moment(refDT).dayOfYear(dayOfYear);
|
||||
if (!derivedByweekday.includes(currentDate.isoWeekday())) return [];
|
||||
return getDayOfRecurrences({ refDT: currentDate, byhour, byminute, bysecond });
|
||||
});
|
||||
};
|
||||
|
||||
const getMonthOfRecurrences = function ({
|
||||
refDT,
|
||||
wkst,
|
||||
bymonthday,
|
||||
bymonth,
|
||||
byweekday,
|
||||
byhour,
|
||||
byminute,
|
||||
bysecond,
|
||||
bysetpos,
|
||||
}: IterOptions) {
|
||||
const derivedByweekday = byweekday ?? ISO_WEEKDAYS;
|
||||
const currentMonth = refDT.month();
|
||||
if (bymonth && !bymonth.includes(currentMonth)) return [];
|
||||
|
||||
let derivedBymonthday = bymonthday ?? [refDT.date()];
|
||||
if (bysetpos) {
|
||||
const firstOfMonth = moment(refDT).month(currentMonth).date(1);
|
||||
const dowLookup: Record<Weekday, number[]> = {
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
5: [],
|
||||
6: [],
|
||||
7: [],
|
||||
};
|
||||
const trackedDate = firstOfMonth;
|
||||
while (trackedDate.month() === currentMonth) {
|
||||
const currentDow = trackedDate.isoWeekday() as Weekday;
|
||||
dowLookup[currentDow].push(trackedDate.date());
|
||||
trackedDate.add(1, 'd');
|
||||
}
|
||||
const sortedByweekday = sortByweekday({ wkst, byweekday: derivedByweekday });
|
||||
const bymonthdayFromPos = bysetpos.map((pos, i) => {
|
||||
const correspondingWeekday = sortedByweekday[i];
|
||||
const lookup = dowLookup[correspondingWeekday];
|
||||
if (pos > 0) return [lookup[pos - 1], pos];
|
||||
return [lookup.slice(pos)[0], pos];
|
||||
});
|
||||
|
||||
const posPositions = [
|
||||
// Start with positive numbers in ascending order
|
||||
...bymonthdayFromPos
|
||||
.filter(([, p]) => p > 0)
|
||||
.sort(([, a], [, b]) => a - b)
|
||||
.map(([date]) => date),
|
||||
];
|
||||
const negPositions = [
|
||||
// then negative numbers in descending order]
|
||||
...bymonthdayFromPos
|
||||
.filter(([, p]) => p < 0)
|
||||
.sort(([, a], [, b]) => a - b)
|
||||
.map(([date]) => date),
|
||||
];
|
||||
derivedBymonthday = [...posPositions, ...negPositions];
|
||||
}
|
||||
|
||||
return derivedBymonthday.flatMap((date) => {
|
||||
const currentDate = moment(refDT).date(date);
|
||||
if (!derivedByweekday.includes(currentDate.isoWeekday())) return [];
|
||||
return getDayOfRecurrences({ refDT: currentDate, byhour, byminute, bysecond });
|
||||
});
|
||||
};
|
||||
|
||||
const getWeekOfRecurrences = function ({
|
||||
refDT,
|
||||
wkst = Weekday.MO,
|
||||
byweekday,
|
||||
byhour,
|
||||
byminute,
|
||||
bysecond,
|
||||
}: IterOptions) {
|
||||
const derivedByweekday = byweekday ? sortByweekday({ wkst, byweekday }) : [refDT.isoWeekday()];
|
||||
|
||||
return derivedByweekday.flatMap((day) => {
|
||||
const currentDay = moment(refDT).isoWeekday(day);
|
||||
return getDayOfRecurrences({ refDT: currentDay, byhour, byminute, bysecond });
|
||||
});
|
||||
};
|
||||
|
||||
const getDayOfRecurrences = function ({ refDT, byhour, byminute, bysecond }: IterOptions) {
|
||||
const derivedByhour =
|
||||
byhour ?? (byminute || bysecond ? Array.from(Array(24), (_, i) => i) : [refDT.hour()]);
|
||||
|
||||
return derivedByhour.flatMap((h) => {
|
||||
const currentHour = moment(refDT).hour(h);
|
||||
return getHourOfRecurrences({ refDT: currentHour, byminute, bysecond });
|
||||
});
|
||||
};
|
||||
|
||||
const getHourOfRecurrences = function ({ refDT, byminute, bysecond }: IterOptions) {
|
||||
const derivedByminute =
|
||||
byminute ?? (bysecond ? Array.from(Array(60), (_, i) => i) : [refDT.minute()]);
|
||||
|
||||
return derivedByminute.flatMap((m) => {
|
||||
const currentMinute = moment(refDT).minute(m);
|
||||
return getMinuteOfRecurrences({ refDT: currentMinute, bysecond });
|
||||
});
|
||||
};
|
||||
|
||||
const getMinuteOfRecurrences = function ({ refDT, bysecond }: IterOptions) {
|
||||
const derivedBysecond = bysecond ?? [refDT.second()];
|
||||
|
||||
return derivedBysecond.map((s) => {
|
||||
return moment(refDT).second(s);
|
||||
});
|
||||
};
|
19
packages/kbn-rrule/tsconfig.json
Normal file
19
packages/kbn-rrule/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
}
|
|
@ -1098,6 +1098,8 @@
|
|||
"@kbn/rollup-plugin/*": ["x-pack/plugins/rollup/*"],
|
||||
"@kbn/routing-example-plugin": ["examples/routing_example"],
|
||||
"@kbn/routing-example-plugin/*": ["examples/routing_example/*"],
|
||||
"@kbn/rrule": ["packages/kbn-rrule"],
|
||||
"@kbn/rrule/*": ["packages/kbn-rrule/*"],
|
||||
"@kbn/rule-data-utils": ["packages/kbn-rule-data-utils"],
|
||||
"@kbn/rule-data-utils/*": ["packages/kbn-rule-data-utils/*"],
|
||||
"@kbn/rule-registry-plugin": ["x-pack/plugins/rule_registry"],
|
||||
|
|
|
@ -5,26 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { WeekdayStr } from 'rrule';
|
||||
import type { WeekdayStr, Options } from '@kbn/rrule';
|
||||
|
||||
export type RRuleParams = Partial<RRuleRecord> & Pick<RRuleRecord, 'dtstart' | 'tzid'>;
|
||||
|
||||
// An iCal RRULE to define a recurrence schedule, see https://github.com/jakubroztocil/rrule for the spec
|
||||
export interface RRuleRecord {
|
||||
export type RRuleRecord = Omit<Options, 'dtstart' | 'byweekday' | 'wkst' | 'until'> & {
|
||||
dtstart: string;
|
||||
tzid: string;
|
||||
freq?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
until?: string;
|
||||
count?: number;
|
||||
interval?: number;
|
||||
byweekday?: Array<WeekdayStr | string | number>;
|
||||
wkst?: WeekdayStr;
|
||||
byweekday?: Array<string | number>;
|
||||
bymonth?: number[];
|
||||
bysetpos?: number[];
|
||||
bymonthday: number[];
|
||||
byyearday: number[];
|
||||
byweekno: number[];
|
||||
byhour: number[];
|
||||
byminute: number[];
|
||||
bysecond: number[];
|
||||
}
|
||||
until?: string;
|
||||
};
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, waitFor, within } from '@testing-library/react';
|
||||
import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { AppMockRenderer, createAppMockRenderer } from '../../../../lib/test_utils';
|
||||
import { FormProps, schema } from '../schema';
|
||||
import { CustomRecurringSchedule } from './custom_recurring_schedule';
|
||||
import { EndsOptions, Frequency } from '../../constants';
|
||||
import { EndsOptions } from '../../constants';
|
||||
|
||||
const initialValue: FormProps = {
|
||||
title: 'test',
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React, { useMemo } from 'react';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import moment from 'moment';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
|
@ -17,7 +18,7 @@ import {
|
|||
MultiButtonGroupFieldValue,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFormLabel, EuiSpacer } from '@elastic/eui';
|
||||
import { CREATE_FORM_CUSTOM_FREQUENCY, Frequency, WEEKDAY_OPTIONS } from '../../constants';
|
||||
import { CREATE_FORM_CUSTOM_FREQUENCY, WEEKDAY_OPTIONS } from '../../constants';
|
||||
import * as i18n from '../../translations';
|
||||
import { getInitialByWeekday } from '../../helpers/get_initial_by_weekday';
|
||||
import { getWeekdayInfo } from '../../helpers/get_weekday_info';
|
||||
|
@ -105,7 +106,7 @@ export const CustomRecurringSchedule: React.FC = React.memo(() => {
|
|||
<EuiSpacer size="s" />
|
||||
</>
|
||||
) : null}
|
||||
{recurringSchedule?.customFrequency === Frequency.WEEKLY ||
|
||||
{Number(recurringSchedule?.customFrequency) === Frequency.WEEKLY ||
|
||||
recurringSchedule?.frequency === Frequency.DAILY ? (
|
||||
<UseField
|
||||
path="recurringSchedule.byweekday"
|
||||
|
@ -137,7 +138,7 @@ export const CustomRecurringSchedule: React.FC = React.memo(() => {
|
|||
/>
|
||||
) : null}
|
||||
|
||||
{recurringSchedule?.customFrequency === Frequency.MONTHLY ? (
|
||||
{Number(recurringSchedule?.customFrequency) === Frequency.MONTHLY ? (
|
||||
<UseField
|
||||
path="recurringSchedule.bymonth"
|
||||
componentProps={{
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { fireEvent, within } from '@testing-library/react';
|
||||
import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { AppMockRenderer, createAppMockRenderer } from '../../../../lib/test_utils';
|
||||
import { FormProps, schema } from '../schema';
|
||||
import { RecurringSchedule } from './recurring_schedule';
|
||||
import { EndsOptions, Frequency } from '../../constants';
|
||||
import { EndsOptions } from '../../constants';
|
||||
|
||||
const initialValue: FormProps = {
|
||||
title: 'test',
|
||||
|
|
|
@ -21,12 +21,12 @@ import {
|
|||
useFormData,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { getWeekdayInfo } from '../../helpers/get_weekday_info';
|
||||
import {
|
||||
DEFAULT_FREQUENCY_OPTIONS,
|
||||
DEFAULT_PRESETS,
|
||||
EndsOptions,
|
||||
Frequency,
|
||||
RECURRENCE_END_OPTIONS,
|
||||
} from '../../constants';
|
||||
import * as i18n from '../../translations';
|
||||
|
|
|
@ -9,8 +9,9 @@ import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form
|
|||
import { FIELD_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import * as i18n from '../translations';
|
||||
import { EndsOptions, Frequency } from '../constants';
|
||||
import { EndsOptions, MaintenanceWindowFrequency } from '../constants';
|
||||
|
||||
const { emptyField } = fieldValidators;
|
||||
|
||||
|
@ -24,12 +25,12 @@ export interface FormProps {
|
|||
}
|
||||
|
||||
export interface RecurringScheduleFormProps {
|
||||
frequency: Frequency | 'CUSTOM';
|
||||
frequency: MaintenanceWindowFrequency | 'CUSTOM';
|
||||
interval?: number;
|
||||
ends: string;
|
||||
until?: string;
|
||||
count?: number;
|
||||
customFrequency?: Frequency;
|
||||
customFrequency?: MaintenanceWindowFrequency;
|
||||
byweekday?: Record<string, boolean>;
|
||||
bymonth?: string;
|
||||
}
|
||||
|
|
|
@ -5,17 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { invert, mapValues } from 'lodash';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import moment from 'moment';
|
||||
import * as i18n from './translations';
|
||||
import { ISO_WEEKDAYS, MaintenanceWindowStatus } from '../../../common';
|
||||
|
||||
// TODO - consolidate enum with backend
|
||||
export enum Frequency {
|
||||
YEARLY = '0',
|
||||
MONTHLY = '1',
|
||||
WEEKLY = '2',
|
||||
DAILY = '3',
|
||||
}
|
||||
export type MaintenanceWindowFrequency = Extract<
|
||||
Frequency,
|
||||
Frequency.YEARLY | Frequency.MONTHLY | Frequency.WEEKLY | Frequency.DAILY
|
||||
>;
|
||||
|
||||
export const DEFAULT_FREQUENCY_OPTIONS = [
|
||||
{
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
import moment from 'moment';
|
||||
|
||||
import { Frequency } from '../constants';
|
||||
import { RRuleFrequency } from '../types';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { convertFromMaintenanceWindowToForm } from './convert_from_maintenance_window_to_form';
|
||||
|
||||
describe('convertFromMaintenanceWindowToForm', () => {
|
||||
|
@ -25,7 +24,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
|
@ -46,7 +45,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
},
|
||||
|
@ -76,7 +75,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
until,
|
||||
|
@ -106,7 +105,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
count: 3,
|
||||
|
@ -136,7 +135,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
},
|
||||
|
@ -164,7 +163,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
byweekday: ['+4WE'],
|
||||
},
|
||||
|
@ -192,7 +191,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 1,
|
||||
bymonth: [3],
|
||||
bymonthday: [22],
|
||||
|
@ -220,7 +219,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
},
|
||||
});
|
||||
|
@ -247,7 +246,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE', 'TH'],
|
||||
},
|
||||
|
@ -276,7 +275,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
bymonthday: [22],
|
||||
},
|
||||
|
@ -305,7 +304,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
|
|||
rRule: {
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 3,
|
||||
bymonth: [3],
|
||||
bymonthday: [22],
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { has } from 'lodash';
|
||||
import { MaintenanceWindow } from '../types';
|
||||
import { EndsOptions, Frequency } from '../constants';
|
||||
import { EndsOptions, MaintenanceWindowFrequency } from '../constants';
|
||||
import { FormProps, RecurringScheduleFormProps } from '../components/schema';
|
||||
import { getInitialByWeekday } from './get_initial_by_weekday';
|
||||
import { RRuleParams } from '../../../../common';
|
||||
|
@ -31,7 +32,7 @@ export const convertFromMaintenanceWindowToForm = (
|
|||
|
||||
const rRule = maintenanceWindow.rRule;
|
||||
const isCustomFrequency = isCustom(rRule);
|
||||
const frequency = rRule.freq?.toString() as Frequency;
|
||||
const frequency = rRule.freq as MaintenanceWindowFrequency;
|
||||
const ends = rRule.until
|
||||
? EndsOptions.ON_DATE
|
||||
: rRule.count
|
||||
|
@ -74,7 +75,7 @@ export const convertFromMaintenanceWindowToForm = (
|
|||
};
|
||||
|
||||
const isCustom = (rRule: RRuleParams) => {
|
||||
const freq = rRule.freq?.toString() as Frequency;
|
||||
const freq = rRule.freq;
|
||||
// interval is greater than 1
|
||||
if (rRule.interval && rRule.interval > 1) {
|
||||
return true;
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import { Frequency } from '../constants';
|
||||
import { RRuleFrequency } from '../types';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { convertToRRule } from './convert_to_rrule';
|
||||
|
||||
describe('convertToRRule', () => {
|
||||
|
@ -22,7 +20,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
@ -37,7 +35,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
});
|
||||
|
@ -55,7 +53,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
until,
|
||||
|
@ -73,7 +71,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
count: 3,
|
||||
|
@ -89,7 +87,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
});
|
||||
|
@ -104,7 +102,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
byweekday: ['+4WE'],
|
||||
});
|
||||
|
@ -119,7 +117,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 1,
|
||||
bymonth: [3],
|
||||
bymonthday: [22],
|
||||
|
@ -137,7 +135,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
});
|
||||
});
|
||||
|
@ -154,7 +152,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE', 'TH'],
|
||||
});
|
||||
|
@ -172,7 +170,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
bymonthday: [22],
|
||||
});
|
||||
|
@ -190,7 +188,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
byweekday: ['+4WE'],
|
||||
});
|
||||
|
@ -207,7 +205,7 @@ describe('convertToRRule', () => {
|
|||
expect(rRule).toEqual({
|
||||
dtstart: startDate.toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 3,
|
||||
bymonth: [3],
|
||||
bymonthday: [22],
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import { Moment } from 'moment';
|
||||
import { RRuleFrequency, RRuleFrequencyMap } from '../types';
|
||||
import { Frequency, ISO_WEEKDAYS_TO_RRULE } from '../constants';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { ISO_WEEKDAYS_TO_RRULE } from '../constants';
|
||||
import { getNthByWeekday } from './get_nth_by_weekday';
|
||||
import { RecurringScheduleFormProps } from '../components/schema';
|
||||
import { getPresets } from './get_presets';
|
||||
|
@ -30,7 +30,7 @@ export const convertToRRule = (
|
|||
...rRule,
|
||||
// default to yearly and a count of 1
|
||||
// if the maintenance window is not recurring
|
||||
freq: RRuleFrequency.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
count: 1,
|
||||
};
|
||||
|
||||
|
@ -39,8 +39,8 @@ export const convertToRRule = (
|
|||
form = { ...recurringForm, ...presets[recurringForm.frequency] };
|
||||
}
|
||||
|
||||
const frequency = form.customFrequency ? form.customFrequency : (form.frequency as Frequency);
|
||||
rRule.freq = RRuleFrequencyMap[frequency];
|
||||
const frequency = form.customFrequency ?? (form.frequency as Frequency);
|
||||
rRule.freq = frequency;
|
||||
|
||||
rRule.interval = form.interval;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { Moment } from 'moment';
|
||||
import { Frequency } from '../constants';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { getInitialByWeekday } from './get_initial_by_weekday';
|
||||
|
||||
export const getPresets = (startDate: Moment) => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import moment from 'moment';
|
||||
|
||||
import { Frequency } from '../constants';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { getPresets } from './get_presets';
|
||||
import { recurringSummary } from './recurring_summary';
|
||||
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
*/
|
||||
|
||||
import moment, { Moment } from 'moment';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import * as i18n from '../translations';
|
||||
import { Frequency, ISO_WEEKDAYS_TO_RRULE, RRULE_WEEKDAYS_TO_ISO_WEEKDAYS } from '../constants';
|
||||
import {
|
||||
MaintenanceWindowFrequency,
|
||||
ISO_WEEKDAYS_TO_RRULE,
|
||||
RRULE_WEEKDAYS_TO_ISO_WEEKDAYS,
|
||||
} from '../constants';
|
||||
import { monthDayDate } from './month_day_date';
|
||||
import { getNthByWeekday } from './get_nth_by_weekday';
|
||||
import { RecurringScheduleFormProps } from '../components/schema';
|
||||
|
@ -15,7 +20,7 @@ import { RecurringScheduleFormProps } from '../components/schema';
|
|||
export const recurringSummary = (
|
||||
startDate: Moment,
|
||||
recurringSchedule: RecurringScheduleFormProps | undefined,
|
||||
presets: Record<Frequency, Partial<RecurringScheduleFormProps>>
|
||||
presets: Record<MaintenanceWindowFrequency, Partial<RecurringScheduleFormProps>>
|
||||
) => {
|
||||
if (!recurringSchedule) return '';
|
||||
|
||||
|
@ -25,9 +30,8 @@ export const recurringSummary = (
|
|||
schedule = { ...recurringSchedule, ...presets[recurringSchedule.frequency] };
|
||||
}
|
||||
|
||||
const frequency = schedule.customFrequency
|
||||
? schedule.customFrequency
|
||||
: (schedule.frequency as Frequency);
|
||||
const frequency =
|
||||
schedule.customFrequency ?? (schedule.frequency as MaintenanceWindowFrequency);
|
||||
const interval = schedule.interval || 1;
|
||||
const frequencySummary = i18n.CREATE_FORM_FREQUENCY_SUMMARY(interval)[frequency];
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Moment } from 'moment';
|
||||
import { Frequency } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { monthDayDate } from './helpers/month_day_date';
|
||||
|
||||
export const MAINTENANCE_WINDOWS = i18n.translate('xpack.alerting.maintenanceWindows', {
|
||||
|
|
|
@ -5,23 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import {
|
||||
MaintenanceWindow as MaintenanceWindowServerSide,
|
||||
MaintenanceWindowModificationMetadata,
|
||||
} from '../../../common';
|
||||
|
||||
export enum RRuleFrequency {
|
||||
YEARLY = 0,
|
||||
MONTHLY = 1,
|
||||
WEEKLY = 2,
|
||||
DAILY = 3,
|
||||
}
|
||||
|
||||
export const RRuleFrequencyMap = {
|
||||
'0': RRuleFrequency.YEARLY,
|
||||
'1': RRuleFrequency.MONTHLY,
|
||||
'2': RRuleFrequency.WEEKLY,
|
||||
'3': RRuleFrequency.DAILY,
|
||||
'0': Frequency.YEARLY,
|
||||
'1': Frequency.MONTHLY,
|
||||
'2': Frequency.WEEKLY,
|
||||
'3': Frequency.DAILY,
|
||||
};
|
||||
|
||||
export type MaintenanceWindow = Pick<MaintenanceWindowServerSide, 'title' | 'duration' | 'rRule'>;
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { RRule } from 'rrule';
|
||||
import moment from 'moment-timezone';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { isRuleSnoozed } from './is_rule_snoozed';
|
||||
import { RRuleRecord } from '../types';
|
||||
|
||||
|
@ -14,6 +15,7 @@ const DATE_9999 = '9999-12-31T12:34:56.789Z';
|
|||
const DATE_1970 = '1970-01-01T00:00:00.000Z';
|
||||
const DATE_2019 = '2019-01-01T00:00:00.000Z';
|
||||
const DATE_2019_PLUS_6_HOURS = '2019-01-01T06:00:00.000Z';
|
||||
const DATE_2019_IN_ASIA = '2018-12-31T16:00:00.000Z';
|
||||
const DATE_2020 = '2020-01-01T00:00:00.000Z';
|
||||
const DATE_2020_MINUS_1_HOUR = '2019-12-31T23:00:00.000Z';
|
||||
const DATE_2020_MINUS_1_MONTH = '2019-12-01T00:00:00.000Z';
|
||||
|
@ -94,7 +96,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2019,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
|
@ -106,7 +108,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2019_PLUS_6_HOURS,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
|
@ -118,7 +120,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2020_MINUS_1_HOUR,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
|
@ -131,7 +133,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 8761,
|
||||
|
@ -145,7 +147,7 @@ describe('isRuleSnoozed', () => {
|
|||
duration: 60 * 1000,
|
||||
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 25,
|
||||
|
@ -159,7 +161,7 @@ describe('isRuleSnoozed', () => {
|
|||
duration: 60 * 1000,
|
||||
|
||||
rRule: {
|
||||
freq: RRule.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 60,
|
||||
|
@ -175,7 +177,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
until: DATE_9999,
|
||||
|
@ -188,7 +190,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
until: DATE_2020_MINUS_1_HOUR,
|
||||
|
@ -204,7 +206,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['MO', 'WE', 'FR'], // Jan 1 2020 was a Wednesday
|
||||
|
@ -217,7 +219,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['TU', 'TH', 'SA', 'SU'],
|
||||
|
@ -230,7 +232,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['MO', 'WE', 'FR'],
|
||||
|
@ -244,7 +246,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['MO', 'WE', 'FR'],
|
||||
|
@ -261,7 +263,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['+1WE'], // Jan 1 2020 was the first Wednesday of the month
|
||||
|
@ -274,7 +276,7 @@ describe('isRuleSnoozed', () => {
|
|||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
byweekday: ['+2WE'],
|
||||
|
@ -286,35 +288,39 @@ describe('isRuleSnoozed', () => {
|
|||
});
|
||||
|
||||
test('using a timezone, returns as expected for a recurring snooze on a day of the week', () => {
|
||||
const snoozeScheduleA = [
|
||||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
tzid: 'Asia/Taipei',
|
||||
dtstart: DATE_2019,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
];
|
||||
try {
|
||||
const snoozeScheduleA = [
|
||||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
tzid: 'Asia/Taipei',
|
||||
dtstart: DATE_2019_IN_ASIA,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
];
|
||||
|
||||
expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true);
|
||||
const snoozeScheduleB = [
|
||||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
byhour: [0],
|
||||
byminute: [0],
|
||||
tzid: 'UTC',
|
||||
dtstart: DATE_2019,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
];
|
||||
expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(true);
|
||||
expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(false);
|
||||
const snoozeScheduleB = [
|
||||
{
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['WE'],
|
||||
byhour: [0],
|
||||
byminute: [0],
|
||||
tzid: 'UTC',
|
||||
dtstart: DATE_2019,
|
||||
} as RRuleRecord,
|
||||
},
|
||||
];
|
||||
expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(true);
|
||||
} catch (e) {
|
||||
throw new Error(`In timezone ${process.env.TZ ?? moment.tz.guess()}: ${e}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('returns as expected for a manually skipped recurring snooze', () => {
|
||||
|
@ -324,7 +330,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2019,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
skipRecurrences: [DATE_2020],
|
||||
|
@ -337,7 +343,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2019,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
skipRecurrences: [DATE_2020_MINUS_1_MONTH],
|
||||
|
@ -350,7 +356,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2020,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
skipRecurrences: [DATE_2020],
|
||||
|
@ -363,7 +369,7 @@ describe('isRuleSnoozed', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2020_MINUS_6_HOURS,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 5,
|
||||
} as RRuleRecord,
|
||||
skipRecurrences: [DATE_2020_MINUS_1_HOUR],
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './parse_by_weekday';
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ByWeekday, rrulestr } from 'rrule';
|
||||
|
||||
export function parseByWeekday(byweekday: Array<string | number>): ByWeekday[] {
|
||||
const rRuleString = `RRULE:BYDAY=${byweekday.join(',')}`;
|
||||
const parsedRRule = rrulestr(rRuleString);
|
||||
return parsedRRule.origOptions.byweekday as ByWeekday[];
|
||||
}
|
|
@ -7,4 +7,3 @@
|
|||
|
||||
export { isSnoozeActive } from './is_snooze_active';
|
||||
export { isSnoozeExpired } from './is_snooze_expired';
|
||||
export { utcToLocalUtc, localUtcToUtc } from './timezone_helpers';
|
||||
|
|
|
@ -6,20 +6,20 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import sinon from 'sinon';
|
||||
import { RRuleRecord } from '../../types';
|
||||
import { isSnoozeActive } from './is_snooze_active';
|
||||
|
||||
let fakeTimer: sinon.SinonFakeTimers;
|
||||
|
||||
describe('isSnoozeExpired', () => {
|
||||
describe('isSnoozeActive', () => {
|
||||
afterAll(() => fakeTimer.restore());
|
||||
|
||||
test('snooze is NOT active byweekday', () => {
|
||||
// Set the current time as:
|
||||
// - Feb 27 2023 08:15:00 GMT+0000 - Monday
|
||||
fakeTimer = sinon.useFakeTimers(new Date('2023-02-27T06:15:00.000Z'));
|
||||
fakeTimer = sinon.useFakeTimers(new Date('2023-02-27T08:15:00.000Z'));
|
||||
|
||||
// Try to get snooze end time with:
|
||||
// - Start date of: Feb 24 2023 23:00:00 GMT+0000 - Friday
|
||||
|
@ -30,7 +30,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
byweekday: ['SA'],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-24T23:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
@ -54,7 +54,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
byweekday: ['SA'],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-24T23:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
@ -84,7 +84,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
byweekday: ['SA'],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-24T23:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
@ -108,7 +108,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
byweekday: ['SA'],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-24T23:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
@ -117,7 +117,7 @@ describe('isSnoozeExpired', () => {
|
|||
expect(isSnoozeActive(snoozeA)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "9141dc1f-ed85-4656-91e4-119173105432",
|
||||
"lastOccurrence": 2023-03-04T00:00:00.000Z,
|
||||
"lastOccurrence": 2023-03-03T23:00:00.000Z,
|
||||
"snoozeEndTime": 2023-03-06T06:00:00.000Z,
|
||||
}
|
||||
`);
|
||||
|
@ -136,7 +136,7 @@ describe('isSnoozeExpired', () => {
|
|||
const snoozeA = {
|
||||
duration: moment('2023-01', 'YYYY-MM').daysInMonth() * 24 * 60 * 60 * 1000, // 1 month
|
||||
rRule: {
|
||||
freq: 0,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 1,
|
||||
bymonthday: [1],
|
||||
bymonth: [1],
|
||||
|
@ -164,7 +164,7 @@ describe('isSnoozeExpired', () => {
|
|||
bymonthday: [1],
|
||||
bymonth: [1],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
dtstart: '2023-01-01T00:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
@ -195,7 +195,7 @@ describe('isSnoozeExpired', () => {
|
|||
bymonthday: [1],
|
||||
bymonth: [1],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
dtstart: '2023-01-01T00:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
@ -221,7 +221,7 @@ describe('isSnoozeExpired', () => {
|
|||
bymonthday: [1],
|
||||
bymonth: [1],
|
||||
tzid: 'Europe/Madrid',
|
||||
freq: RRule.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
dtstart: '2023-01-01T00:00:00.000Z',
|
||||
} as RRuleRecord,
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RRule, Weekday } from 'rrule';
|
||||
import { RRule, Weekday } from '@kbn/rrule';
|
||||
import { RuleSnoozeSchedule } from '../../types';
|
||||
import { parseByWeekday } from '../rrule';
|
||||
import { utcToLocalUtc, localUtcToUtc } from './timezone_helpers';
|
||||
|
||||
const MAX_TIMESTAMP = 8640000000000000;
|
||||
|
||||
|
@ -32,32 +30,24 @@ export function isSnoozeActive(snooze: RuleSnoozeSchedule) {
|
|||
};
|
||||
|
||||
// Check to see if now is during a recurrence of the snooze
|
||||
|
||||
const { tzid, ...restRRule } = rRule;
|
||||
const startDate = utcToLocalUtc(new Date(rRule.dtstart), tzid);
|
||||
const nowDate = utcToLocalUtc(new Date(now), tzid);
|
||||
|
||||
try {
|
||||
const rRuleOptions = {
|
||||
...restRRule,
|
||||
dtstart: startDate,
|
||||
until: rRule.until ? utcToLocalUtc(new Date(rRule.until), tzid) : null,
|
||||
wkst: rRule.wkst ? Weekday.fromStr(rRule.wkst) : null,
|
||||
byweekday: rRule.byweekday ? parseByWeekday(rRule.byweekday) : null,
|
||||
...rRule,
|
||||
dtstart: new Date(rRule.dtstart),
|
||||
until: rRule.until ? new Date(rRule.until) : null,
|
||||
byweekday: rRule.byweekday ?? null,
|
||||
wkst: rRule.wkst ? Weekday[rRule.wkst] : null,
|
||||
};
|
||||
|
||||
const recurrenceRule = new RRule(rRuleOptions);
|
||||
const lastOccurrence = recurrenceRule.before(nowDate, true);
|
||||
const lastOccurrence = recurrenceRule.before(new Date(now));
|
||||
|
||||
if (!lastOccurrence) return null;
|
||||
// Check if the current recurrence has been skipped manually
|
||||
if (snooze.skipRecurrences?.includes(lastOccurrence.toISOString())) return null;
|
||||
const lastOccurrenceEndTime = lastOccurrence.getTime() + duration;
|
||||
if (nowDate.getTime() < lastOccurrenceEndTime)
|
||||
return {
|
||||
lastOccurrence,
|
||||
snoozeEndTime: localUtcToUtc(new Date(lastOccurrenceEndTime), tzid),
|
||||
id,
|
||||
};
|
||||
if (now < lastOccurrenceEndTime)
|
||||
return { lastOccurrence, snoozeEndTime: new Date(lastOccurrenceEndTime), id };
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to process RRule ${rRule}: ${e}`);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { isSnoozeExpired } from './is_snooze_expired';
|
||||
import { RRuleRecord } from '../../types';
|
||||
|
||||
|
@ -84,7 +84,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2019,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
};
|
||||
|
@ -95,7 +95,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2019_PLUS_6_HOURS,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
};
|
||||
|
@ -105,7 +105,7 @@ describe('isSnoozeExpired', () => {
|
|||
rRule: {
|
||||
dtstart: DATE_2020_MINUS_1_HOUR,
|
||||
tzid: 'UTC',
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
} as RRuleRecord,
|
||||
};
|
||||
|
@ -116,7 +116,7 @@ describe('isSnoozeExpired', () => {
|
|||
const snoozeA = {
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 8761,
|
||||
|
@ -128,7 +128,7 @@ describe('isSnoozeExpired', () => {
|
|||
duration: 60 * 1000,
|
||||
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 25,
|
||||
|
@ -140,7 +140,7 @@ describe('isSnoozeExpired', () => {
|
|||
duration: 60 * 1000,
|
||||
|
||||
rRule: {
|
||||
freq: RRule.YEARLY,
|
||||
freq: Frequency.YEARLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
count: 30,
|
||||
|
@ -155,7 +155,7 @@ describe('isSnoozeExpired', () => {
|
|||
const snoozeA = {
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
until: DATE_9999,
|
||||
|
@ -166,7 +166,7 @@ describe('isSnoozeExpired', () => {
|
|||
const snoozeB = {
|
||||
duration: 60 * 1000,
|
||||
rRule: {
|
||||
freq: RRule.HOURLY,
|
||||
freq: Frequency.HOURLY,
|
||||
interval: 1,
|
||||
tzid: 'UTC',
|
||||
until: DATE_2020_MINUS_1_HOUR,
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RRule, Weekday } from 'rrule';
|
||||
import { RRule, Weekday } from '@kbn/rrule';
|
||||
import { RuleSnoozeSchedule } from '../../types';
|
||||
import { isSnoozeActive } from './is_snooze_active';
|
||||
import { parseByWeekday } from '../rrule';
|
||||
|
||||
export function isSnoozeExpired(snooze: RuleSnoozeSchedule) {
|
||||
if (isSnoozeActive(snooze)) {
|
||||
|
@ -23,12 +22,12 @@ export function isSnoozeExpired(snooze: RuleSnoozeSchedule) {
|
|||
...rRule,
|
||||
dtstart: new Date(rRule.dtstart),
|
||||
until: rRule.until ? new Date(rRule.until) : null,
|
||||
wkst: rRule.wkst ? Weekday.fromStr(rRule.wkst) : null,
|
||||
byweekday: rRule.byweekday ? parseByWeekday(rRule.byweekday) : null,
|
||||
byweekday: rRule.byweekday ?? null,
|
||||
wkst: rRule.wkst ? Weekday[rRule.wkst] : null,
|
||||
};
|
||||
|
||||
const recurrenceRule = new RRule(rRuleOptions);
|
||||
const nextOccurrence = recurrenceRule.after(new Date(now), true);
|
||||
const nextOccurrence = recurrenceRule.after(new Date(now));
|
||||
return !nextOccurrence;
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to process RRule ${rRule}: ${e}`);
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
/**
|
||||
* Converts the UTC date into the user's local time zone, but still in UTC.
|
||||
* This must be done because rrule does not care about timezones, so for the result
|
||||
* to be correct, we must ensure everything is timezone agnostic.
|
||||
*
|
||||
* example: 2023-03-29 08:00:00 CET -> 2023-03-29 08:00:00 UTC
|
||||
*/
|
||||
export const utcToLocalUtc = (date: Date, tz: string) => {
|
||||
const localTime = moment(date).tz(tz);
|
||||
const localTimeInUTC = moment(localTime).tz('UTC', true);
|
||||
return localTimeInUTC.utc().toDate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the local date in UTC back into actual UTC. After rrule does its thing,
|
||||
* we would still like to keep everything in UTC in the business logic, hence why we
|
||||
* need to convert everything back
|
||||
*
|
||||
* Example: 2023-03-29 08:00:00 UTC (from the utcToLocalUtc output) -> 2023-03-29 06:00:00 UTC (Real UTC)
|
||||
*/
|
||||
export const localUtcToUtc = (date: Date, tz: string) => {
|
||||
const localTimeString = moment.utc(date).format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||
return moment.tz(localTimeString, tz).utc().toDate();
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Frequency } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import moment from 'moment';
|
||||
import { RuleSnoozeSchedule } from '../types';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { generateMaintenanceWindowEvents } from './generate_maintenance_window_events';
|
||||
|
||||
describe('generateMaintenanceWindowEvents', () => {
|
||||
|
@ -24,7 +24,7 @@ describe('generateMaintenanceWindowEvents', () => {
|
|||
.toISOString(),
|
||||
rRule: {
|
||||
tzid: 'UTC',
|
||||
freq: RRule.DAILY,
|
||||
freq: Frequency.DAILY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-27T00:00:00.000Z',
|
||||
},
|
||||
|
@ -55,7 +55,7 @@ describe('generateMaintenanceWindowEvents', () => {
|
|||
.toISOString(),
|
||||
rRule: {
|
||||
tzid: 'UTC',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-27T00:00:00.000Z',
|
||||
},
|
||||
|
@ -86,7 +86,7 @@ describe('generateMaintenanceWindowEvents', () => {
|
|||
.toISOString(),
|
||||
rRule: {
|
||||
tzid: 'UTC',
|
||||
freq: RRule.MONTHLY,
|
||||
freq: Frequency.MONTHLY,
|
||||
interval: 1,
|
||||
dtstart: '2023-02-27T00:00:00.000Z',
|
||||
},
|
||||
|
@ -116,7 +116,7 @@ describe('generateMaintenanceWindowEvents', () => {
|
|||
.toISOString(),
|
||||
rRule: {
|
||||
tzid: 'UTC',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
interval: 1,
|
||||
byweekday: ['TU', 'TH'],
|
||||
dtstart: '2023-02-27T00:00:00.000Z',
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment-timezone';
|
||||
import { RRule, Weekday } from 'rrule';
|
||||
import { parseByWeekday } from '../lib/rrule';
|
||||
import { RRule, Weekday } from '@kbn/rrule';
|
||||
import { RRuleParams, MaintenanceWindowSOAttributes, DateRange } from '../../common';
|
||||
import { utcToLocalUtc, localUtcToUtc } from '../lib/snooze';
|
||||
|
||||
export interface GenerateMaintenanceWindowEventsParams {
|
||||
rRule: RRuleParams;
|
||||
|
@ -23,28 +21,27 @@ export const generateMaintenanceWindowEvents = ({
|
|||
expirationDate,
|
||||
duration,
|
||||
}: GenerateMaintenanceWindowEventsParams) => {
|
||||
const { dtstart, until, wkst, byweekday, tzid, ...rest } = rRule;
|
||||
const { dtstart, until, wkst, byweekday, ...rest } = rRule;
|
||||
|
||||
const startDate = utcToLocalUtc(new Date(dtstart), tzid);
|
||||
const endDate = utcToLocalUtc(new Date(expirationDate), tzid);
|
||||
const startDate = new Date(dtstart);
|
||||
const endDate = new Date(expirationDate);
|
||||
|
||||
const rRuleOptions = {
|
||||
...rest,
|
||||
dtstart: startDate,
|
||||
until: until ? utcToLocalUtc(new Date(until), tzid) : null,
|
||||
wkst: wkst ? Weekday.fromStr(wkst) : null,
|
||||
byweekday: byweekday ? parseByWeekday(byweekday) : null,
|
||||
until: until ? new Date(until) : null,
|
||||
wkst: wkst ? Weekday[wkst] : null,
|
||||
byweekday: byweekday ?? null,
|
||||
};
|
||||
|
||||
try {
|
||||
const recurrenceRule = new RRule(rRuleOptions);
|
||||
const occurrenceDates = recurrenceRule.between(startDate, endDate, true);
|
||||
const occurrenceDates = recurrenceRule.between(startDate, endDate);
|
||||
|
||||
return occurrenceDates.map((date) => {
|
||||
const utcDate = localUtcToUtc(date, tzid);
|
||||
return {
|
||||
gte: utcDate.toISOString(),
|
||||
lte: moment.utc(utcDate).add(duration, 'ms').toISOString(),
|
||||
gte: date.toISOString(),
|
||||
lte: moment(date).add(duration, 'ms').toISOString(),
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { archive } from './archive';
|
||||
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { SavedObjectsUpdateResponse, SavedObject } from '@kbn/core/server';
|
||||
|
@ -153,7 +153,7 @@ describe('MaintenanceWindowClient - archive', () => {
|
|||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 5,
|
||||
},
|
||||
events: modifiedEvents,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { finish } from './finish';
|
||||
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { SavedObjectsUpdateResponse, SavedObject } from '@kbn/core/server';
|
||||
|
@ -54,7 +54,7 @@ describe('MaintenanceWindowClient - finish', () => {
|
|||
rRule: {
|
||||
tzid: 'UTC',
|
||||
dtstart: moment().utc().toISOString(),
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
|
@ -110,7 +110,7 @@ describe('MaintenanceWindowClient - finish', () => {
|
|||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 5,
|
||||
},
|
||||
events: modifiedEvents,
|
||||
|
@ -158,7 +158,7 @@ describe('MaintenanceWindowClient - finish', () => {
|
|||
rRule: {
|
||||
tzid: 'UTC',
|
||||
dtstart: moment().utc().toISOString(),
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { MaintenanceWindowSOAttributes } from '../../../common';
|
||||
|
||||
export const getMockMaintenanceWindow = (
|
||||
|
@ -18,7 +18,7 @@ export const getMockMaintenanceWindow = (
|
|||
rRule: {
|
||||
tzid: 'UTC',
|
||||
dtstart: '2023-02-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 2,
|
||||
},
|
||||
events: [
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { update } from './update';
|
||||
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { SavedObject } from '@kbn/core/server';
|
||||
|
@ -28,7 +28,7 @@ const updatedAttributes = {
|
|||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 2,
|
||||
},
|
||||
};
|
||||
|
@ -134,7 +134,7 @@ describe('MaintenanceWindowClient - update', () => {
|
|||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 5,
|
||||
},
|
||||
events: modifiedEvents,
|
||||
|
@ -177,7 +177,7 @@ describe('MaintenanceWindowClient - update', () => {
|
|||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -13,7 +13,11 @@ export const rRuleSchema = schema.object({
|
|||
dtstart: schema.string({ validate: validateSnoozeStartDate }),
|
||||
tzid: schema.string(),
|
||||
freq: schema.maybe(
|
||||
schema.oneOf([schema.literal(0), schema.literal(1), schema.literal(2), schema.literal(3)])
|
||||
schema.number({
|
||||
validate: (freq: number) => {
|
||||
if (freq < 0 || freq > 3) return 'rRule freq must be 0, 1, 2, or 3';
|
||||
},
|
||||
})
|
||||
),
|
||||
interval: schema.maybe(
|
||||
schema.number({
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { RRule } from 'rrule';
|
||||
import { Frequency } from '@kbn/rrule';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
|
@ -36,7 +36,7 @@ const updateParams = {
|
|||
r_rule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: RRule.WEEKLY,
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 10,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": [
|
||||
"server/**/*",
|
||||
|
@ -42,6 +42,7 @@
|
|||
"@kbn/alerting-state-types",
|
||||
"@kbn/alerts-as-data-utils",
|
||||
"@kbn/core-elasticsearch-client-server-mocks",
|
||||
"@kbn/rrule",
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/management-plugin",
|
||||
|
@ -52,9 +53,7 @@
|
|||
"@kbn/doc-links",
|
||||
"@kbn/core-saved-objects-utils-server",
|
||||
"@kbn/core-ui-settings-common",
|
||||
"@kbn/core-capabilities-common",
|
||||
"@kbn/core-capabilities-common"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -4954,6 +4954,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/rrule@link:packages/kbn-rrule":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/rule-data-utils@link:packages/kbn-rule-data-utils":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -20703,7 +20707,7 @@ lru-queue@0.1:
|
|||
dependencies:
|
||||
es5-ext "~0.10.2"
|
||||
|
||||
luxon@^1.21.3, luxon@^1.25.0:
|
||||
luxon@^1.25.0:
|
||||
version "1.28.1"
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0"
|
||||
integrity sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==
|
||||
|
@ -25674,15 +25678,6 @@ robust-predicates@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a"
|
||||
integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==
|
||||
|
||||
rrule@2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.6.4.tgz#7f4f31fda12bc7249bb176c891109a9bc448e035"
|
||||
integrity sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==
|
||||
dependencies:
|
||||
tslib "^1.10.0"
|
||||
optionalDependencies:
|
||||
luxon "^1.21.3"
|
||||
|
||||
rst-selector-parser@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue