Skip to content

Commit

Permalink
use rule meta to persist time unit
Browse files Browse the repository at this point in the history
  • Loading branch information
maximpn committed Jan 15, 2025
1 parent a32193b commit d48d61e
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ describe('TimeDuration', () => {
describe('fromMilliseconds', () => {
it.each([
[5000, new TimeDuration(5, 's')],
[600000, new TimeDuration(10, 'm')],
[25200000, new TimeDuration(7, 'h')],
[777600000, new TimeDuration(9, 'd')],
[600000, new TimeDuration(600, 's')],
[25200000, new TimeDuration(25200, 's')],
[777600000, new TimeDuration(777600, 's')],
[-3000, new TimeDuration(-3, 's')],
[-300000, new TimeDuration(-5, 'm')],
[-18000000, new TimeDuration(-5, 'h')],
[-604800000, new TimeDuration(-7, 'd')],
[-300000, new TimeDuration(-300, 's')],
[-18000000, new TimeDuration(-18000, 's')],
[-604800000, new TimeDuration(-604800, 's')],
])('parses "%s"', (ms, expectedTimeDuration) => {
const result = TimeDuration.fromMilliseconds(ms);

Expand All @@ -40,17 +40,17 @@ describe('TimeDuration', () => {
['-5h', new TimeDuration(-5, 'h')],
['-7d', new TimeDuration(-7, 'd')],
['0s', new TimeDuration(0, 's')],
['0m', new TimeDuration(0, 's')],
['0h', new TimeDuration(0, 's')],
['0d', new TimeDuration(0, 's')],
['0m', new TimeDuration(0, 'm')],
['0h', new TimeDuration(0, 'h')],
['0d', new TimeDuration(0, 'd')],
['+0s', new TimeDuration(0, 's')],
['+0m', new TimeDuration(0, 's')],
['+0h', new TimeDuration(0, 's')],
['+0d', new TimeDuration(0, 's')],
['-0s', new TimeDuration(0, 's')],
['-0m', new TimeDuration(0, 's')],
['-0h', new TimeDuration(0, 's')],
['-0d', new TimeDuration(0, 's')],
['+0m', new TimeDuration(0, 'm')],
['+0h', new TimeDuration(0, 'h')],
['+0d', new TimeDuration(0, 'd')],
['-0s', new TimeDuration(-0, 's')],
['-0m', new TimeDuration(-0, 'm')],
['-0h', new TimeDuration(-0, 'h')],
['-0d', new TimeDuration(-0, 'd')],
])('parses "%s"', (duration, expectedTimeDuration) => {
const result = TimeDuration.parse(duration);

Expand Down Expand Up @@ -160,68 +160,40 @@ describe('TimeDuration', () => {
);
});

describe('toNormalizedTimeDuration', () => {
describe('toDesiredTimeUnit', () => {
it.each([
[new TimeDuration(5, 's'), new TimeDuration(5, 's')],
[new TimeDuration(65, 's'), new TimeDuration(65, 's')],
[new TimeDuration(600, 's'), new TimeDuration(10, 'm')],
[new TimeDuration(650, 's'), new TimeDuration(650, 's')],
[new TimeDuration(90, 'm'), new TimeDuration(90, 'm')],
[new TimeDuration(25200, 's'), new TimeDuration(7, 'h')],
[new TimeDuration(120, 'm'), new TimeDuration(2, 'h')],
[new TimeDuration(36, 'h'), new TimeDuration(36, 'h')],
[new TimeDuration(777600, 's'), new TimeDuration(9, 'd')],
[new TimeDuration(5184000, 's'), new TimeDuration(60, 'd')],
[new TimeDuration(1440, 'm'), new TimeDuration(1, 'd')],
[new TimeDuration(48, 'h'), new TimeDuration(2, 'd')],
[new TimeDuration(-5, 's'), new TimeDuration(-5, 's')],
[new TimeDuration(-65, 's'), new TimeDuration(-65, 's')],
[new TimeDuration(-600, 's'), new TimeDuration(-10, 'm')],
[new TimeDuration(-650, 's'), new TimeDuration(-650, 's')],
[new TimeDuration(-90, 'm'), new TimeDuration(-90, 'm')],
[new TimeDuration(-25200, 's'), new TimeDuration(-7, 'h')],
[new TimeDuration(-120, 'm'), new TimeDuration(-2, 'h')],
[new TimeDuration(-36, 'h'), new TimeDuration(-36, 'h')],
[new TimeDuration(-777600, 's'), new TimeDuration(-9, 'd')],
[new TimeDuration(-5184000, 's'), new TimeDuration(-60, 'd')],
[new TimeDuration(-1440, 'm'), new TimeDuration(-1, 'd')],
[new TimeDuration(-48, 'h'), new TimeDuration(-2, 'd')],
])('converts %j to normalized time duration %j', (timeDuration, expected) => {
const result = timeDuration.toNormalizedTimeDuration();

expect(result).toEqual(expected);
});

it.each([
[new TimeDuration(0, 's')],
[new TimeDuration(0, 'm')],
[new TimeDuration(0, 'h')],
[new TimeDuration(0, 'd')],
])('converts %j to 0s', (timeDuration) => {
const result = timeDuration.toNormalizedTimeDuration();

expect(result).toEqual(new TimeDuration(0, 's'));
});
[new TimeDuration(60, 's'), 'm', new TimeDuration(1, 'm')],
[new TimeDuration(60, 'm'), 'h', new TimeDuration(1, 'h')],
[new TimeDuration(24, 'h'), 'd', new TimeDuration(1, 'd')],
[new TimeDuration(3600, 's'), 'm', new TimeDuration(60, 'm')],
[new TimeDuration(86400, 's'), 'm', new TimeDuration(1440, 'm')],
[new TimeDuration(1440, 'm'), 'h', new TimeDuration(24, 'h')],
[new TimeDuration(1, 'd'), 's', new TimeDuration(86400, 's')],
[new TimeDuration(1, 'd'), 'm', new TimeDuration(1440, 'm')],
[new TimeDuration(1, 'd'), 'h', new TimeDuration(24, 'h')],
[new TimeDuration(1, 'h'), 's', new TimeDuration(3600, 's')],
[new TimeDuration(1, 'h'), 'm', new TimeDuration(60, 'm')],
[new TimeDuration(1, 'm'), 's', new TimeDuration(60, 's')],
] as const)(
'transforms %j to desired time unit %s',
(timeDuration, desiredTimeUnit, expected) => {
const result = timeDuration.toDesiredTimeUnit(desiredTimeUnit);

expect(result).toEqual(expected);
}
);

it.each([
// @ts-expect-error testing invalid unit
[new TimeDuration(1, 'S')],
// @ts-expect-error testing invalid unit
[new TimeDuration(2, 'M')],
// @ts-expect-error testing invalid unit
[new TimeDuration(3, 'H')],
// @ts-expect-error testing invalid unit
[new TimeDuration(4, 'D')],
// @ts-expect-error testing invalid unit
[new TimeDuration(5, 'Y')],
// @ts-expect-error testing invalid unit
[new TimeDuration(7, 'nanos')],
// @ts-expect-error testing invalid unit
[new TimeDuration(8, 'ms')],
// @ts-expect-error testing invalid unit
[new TimeDuration(0, 'invalid')],
])('returns %j unchanged', (timeDuration) => {
const result = timeDuration.toNormalizedTimeDuration();
[new TimeDuration(61, 's'), 'm'],
[new TimeDuration(61, 'm'), 'h'],
[new TimeDuration(25, 'h'), 'd'],
[new TimeDuration(3601, 's'), 'm'],
[new TimeDuration(86401, 's'), 'm'],
[new TimeDuration(1441, 'm'), 'h'],
// @ts-expect-error testing invalid time unit
[new TimeDuration(1441, 'invalid'), 'h'],
] as const)('returns %j as is', (timeDuration, desiredTimeUnit) => {
const result = timeDuration.toDesiredTimeUnit(desiredTimeUnit);

expect(result).toEqual(timeDuration);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class TimeDuration {
* Constructs a time duration from milliseconds. The output is normalized.
*/
static fromMilliseconds(ms: number): TimeDuration {
return new TimeDuration(Math.round(ms / 1000), 's').toNormalizedTimeDuration();
return new TimeDuration(Math.round(ms / 1000), 's');
}

/*
Expand All @@ -41,7 +41,7 @@ export class TimeDuration {
const value = parseInt(matchArray[1], 10);
const unit = matchArray[2] as TimeDuration['unit'];

return new TimeDuration(value, unit).toNormalizedTimeDuration();
return new TimeDuration(value, unit);
}

constructor(public value: number, public unit: TimeDurationUnits) {}
Expand Down Expand Up @@ -70,36 +70,31 @@ export class TimeDuration {
}

/**
* Converts time duration to the largest possible units. E.g.
* - 60s transformed to 1m
* - 3600s transformed to 1h
* - 1440m transformed to 1d
* Tries to transform the current time duration to desired time unit.
* When it's possible returns transformed time duration instance.
* Otherwise returns the same instance.
*/
toNormalizedTimeDuration(): TimeDuration {
toDesiredTimeUnit(desiredTimeUnit: TimeDurationUnits): TimeDuration {
const ms = this.toMilliseconds();

if (ms === undefined) {
return this;
}

if (ms === 0) {
return new TimeDuration(0, 's');
if (desiredTimeUnit === 's' && ms % 1000 === 0) {
return new TimeDuration(ms / 1000, 's');
}

if (ms % (3600000 * 24) === 0) {
return new TimeDuration(ms / (3600000 * 24), 'd');
if (desiredTimeUnit === 'm' && ms % 60000 === 0) {
return new TimeDuration(ms / 60000, 'm');
}

if (ms % 3600000 === 0) {
if (desiredTimeUnit === 'h' && ms % 3600000 === 0) {
return new TimeDuration(ms / 3600000, 'h');
}

if (ms % 60000 === 0) {
return new TimeDuration(ms / 60000, 'm');
}

if (ms % 1000 === 0) {
return new TimeDuration(ms / 1000, 's');
if (desiredTimeUnit === 'd' && ms % (3600000 * 24) === 0) {
return new TimeDuration(ms / (3600000 * 24), 'd');
}

return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ describe('toSimpleRuleSchedule', () => {
],
[
{ interval: '60s', from: 'now-2m', to: 'now' },
{ interval: '60s', lookback: '1m' },
{ interval: '60s', lookback: '60s' },
],
[
{ interval: '60s', from: 'now-2h', to: 'now' },
{ interval: '60s', lookback: '119m' },
{ interval: '60s', lookback: '7140s' },
],
[
{ interval: '60m', from: 'now-3h', to: 'now' },
{ interval: '60m', lookback: '2h' },
{ interval: '60m', lookback: '120m' },
],
[
{ interval: '3600s', from: 'now-5h', to: 'now' },
{ interval: '3600s', lookback: '4h' },
{ interval: '3600s', lookback: '14400s' },
],
[
{ interval: '1m', from: 'now-120s', to: 'now' },
Expand All @@ -55,31 +55,31 @@ describe('toSimpleRuleSchedule', () => {
],
[
{ interval: '30m', from: 'now-30m', to: 'now' },
{ interval: '30m', lookback: '0s' },
{ interval: '30m', lookback: '0m' },
],
[
{ interval: '1h', from: 'now-1h', to: 'now' },
{ interval: '1h', lookback: '0s' },
{ interval: '1h', lookback: '0h' },
],
[
{ interval: '60s', from: 'now-1m', to: 'now' },
{ interval: '60s', lookback: '0s' },
],
[
{ interval: '60m', from: 'now-1h', to: 'now' },
{ interval: '60m', lookback: '0s' },
{ interval: '60m', lookback: '0m' },
],
[
{ interval: '1m', from: 'now-60s', to: 'now' },
{ interval: '1m', lookback: '0s' },
{ interval: '1m', lookback: '0m' },
],
[
{ interval: '1h', from: 'now-60m', to: 'now' },
{ interval: '1h', lookback: '0s' },
{ interval: '1h', lookback: '0h' },
],
[
{ interval: '1h', from: 'now-3600s', to: 'now' },
{ interval: '1h', lookback: '0s' },
{ interval: '1h', lookback: '0h' },
],
[
{ interval: '0s', from: 'now', to: 'now' },
Expand All @@ -91,6 +91,24 @@ describe('toSimpleRuleSchedule', () => {
expect(result).toEqual(expected);
});

it.each([
[
{ interval: '100s', from: 'now-400s', to: 'now', meta: { from: '3m' } },
{ interval: '100s', lookback: '5m' },
],
[
{ interval: '100m', from: 'now-400m', to: 'now', meta: { from: '5h' } },
{ interval: '100m', lookback: '5h' },
],
])(
'transforms %j to simple rule schedule with desired time unit',
(fullRuleSchedule, expected) => {
const result = toSimpleRuleSchedule(fullRuleSchedule);

expect(result).toEqual(expected);
}
);

it.each([
[{ interval: 'invalid', from: 'now-11m', to: 'now' }],
[{ interval: '10m', from: 'invalid', to: 'now' }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
*/

import { calcDateMathDiff } from '@kbn/securitysolution-utils/date_math';
import { TimeDuration as TimeDurationUtil } from '@kbn/securitysolution-utils/time_duration';
import { TimeDuration } from '@kbn/securitysolution-utils/time_duration';
import type { RuleSchedule, SimpleRuleSchedule } from './rule_schedule';

interface RuleScheduleMeta {
meta?: {
from?: string;
};
}

/**
* Transforms RuleSchedule to SimpleRuleSchedule by replacing `from` and `to` with `lookback`.
*
* The transformation is only possible when `to` equals to `now` and result `lookback` is non-negative.
*/
export function toSimpleRuleSchedule(ruleSchedule: RuleSchedule): SimpleRuleSchedule | undefined {
export function toSimpleRuleSchedule(
ruleSchedule: RuleSchedule & RuleScheduleMeta
): SimpleRuleSchedule | undefined {
if (ruleSchedule.to !== 'now') {
return undefined;
}
Expand All @@ -25,8 +33,17 @@ export function toSimpleRuleSchedule(ruleSchedule: RuleSchedule): SimpleRuleSche
return undefined;
}

// Rule meta may contain non-normalized user entered look-back value
// It's required to extract time units to restore desired time units
// in form inputs.
const rawLookBack = TimeDuration.parse(ruleSchedule.meta?.from ?? '');

const interval = TimeDuration.parse(ruleSchedule.interval);

return {
interval: ruleSchedule.interval,
lookback: TimeDurationUtil.fromMilliseconds(lookBackMs).toString(),
lookback: TimeDuration.fromMilliseconds(lookBackMs)
.toDesiredTimeUnit(rawLookBack?.unit ?? interval?.unit ?? 's')
.toString(),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ describe('helpers', () => {
test('returns formatted object as ScheduleStepRuleJson', () => {
const result = formatScheduleStepData(mockData);
const expected: ScheduleStepRuleJson = {
from: 'now-11m',
from: 'now-660s',
to: 'now',
interval: '5m',
meta: {
Expand All @@ -606,7 +606,7 @@ describe('helpers', () => {
delete mockStepData.to;
const result = formatScheduleStepData(mockStepData);
const expected: ScheduleStepRuleJson = {
from: 'now-11m',
from: 'now-660s',
to: 'now',
interval: '5m',
meta: {
Expand All @@ -624,7 +624,7 @@ describe('helpers', () => {
};
const result = formatScheduleStepData(mockStepData);
const expected: ScheduleStepRuleJson = {
from: 'now-11m',
from: 'now-660s',
to: 'now',
interval: '5m',
meta: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ export const formatScheduleStepData = (scheduleData: ScheduleStepRule): Schedule
return {
...formatScheduleData,
meta: {
// User entered look-back value. It's used to persist time units.
from: scheduleData.from,
},
};
Expand Down
Loading

0 comments on commit d48d61e

Please sign in to comment.