mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Improve time window handling and validation (#161978)
This commit is contained in:
parent
8721978c5b
commit
d9f098f210
20 changed files with 165 additions and 169 deletions
|
@ -27,8 +27,6 @@ describe('Duration', () => {
|
|||
expect(new Duration(1, DurationUnit.Day).format()).toBe('1d');
|
||||
expect(new Duration(1, DurationUnit.Week).format()).toBe('1w');
|
||||
expect(new Duration(1, DurationUnit.Month).format()).toBe('1M');
|
||||
expect(new Duration(1, DurationUnit.Quarter).format()).toBe('1Q');
|
||||
expect(new Duration(1, DurationUnit.Year).format()).toBe('1Y');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -39,31 +37,25 @@ describe('Duration', () => {
|
|||
expect(short.isShorterThan(new Duration(1, DurationUnit.Day))).toBe(true);
|
||||
expect(short.isShorterThan(new Duration(1, DurationUnit.Week))).toBe(true);
|
||||
expect(short.isShorterThan(new Duration(1, DurationUnit.Month))).toBe(true);
|
||||
expect(short.isShorterThan(new Duration(1, DurationUnit.Quarter))).toBe(true);
|
||||
expect(short.isShorterThan(new Duration(1, DurationUnit.Year))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when the current duration is longer (or equal) than the other duration', () => {
|
||||
const long = new Duration(1, DurationUnit.Year);
|
||||
const long = new Duration(1, DurationUnit.Month);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Minute))).toBe(false);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Hour))).toBe(false);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Day))).toBe(false);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Week))).toBe(false);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Month))).toBe(false);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Quarter))).toBe(false);
|
||||
expect(long.isShorterThan(new Duration(1, DurationUnit.Year))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLongerOrEqualThan', () => {
|
||||
it('returns true when the current duration is longer or equal than the other duration', () => {
|
||||
const long = new Duration(2, DurationUnit.Year);
|
||||
const long = new Duration(2, DurationUnit.Month);
|
||||
expect(long.isLongerOrEqualThan(new Duration(1, DurationUnit.Hour))).toBe(true);
|
||||
expect(long.isLongerOrEqualThan(new Duration(1, DurationUnit.Day))).toBe(true);
|
||||
expect(long.isLongerOrEqualThan(new Duration(1, DurationUnit.Week))).toBe(true);
|
||||
expect(long.isLongerOrEqualThan(new Duration(1, DurationUnit.Month))).toBe(true);
|
||||
expect(long.isLongerOrEqualThan(new Duration(1, DurationUnit.Quarter))).toBe(true);
|
||||
expect(long.isLongerOrEqualThan(new Duration(1, DurationUnit.Year))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when the current duration is shorter than the other duration', () => {
|
||||
|
@ -73,8 +65,6 @@ describe('Duration', () => {
|
|||
expect(short.isLongerOrEqualThan(new Duration(1, DurationUnit.Day))).toBe(false);
|
||||
expect(short.isLongerOrEqualThan(new Duration(1, DurationUnit.Week))).toBe(false);
|
||||
expect(short.isLongerOrEqualThan(new Duration(1, DurationUnit.Month))).toBe(false);
|
||||
expect(short.isLongerOrEqualThan(new Duration(1, DurationUnit.Quarter))).toBe(false);
|
||||
expect(short.isLongerOrEqualThan(new Duration(1, DurationUnit.Year))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ enum DurationUnit {
|
|||
'Day' = 'd',
|
||||
'Week' = 'w',
|
||||
'Month' = 'M',
|
||||
'Quarter' = 'Q',
|
||||
'Year' = 'Y',
|
||||
}
|
||||
|
||||
class Duration {
|
||||
|
@ -73,10 +71,6 @@ const toDurationUnit = (unit: string): DurationUnit => {
|
|||
return DurationUnit.Week;
|
||||
case 'M':
|
||||
return DurationUnit.Month;
|
||||
case 'Q':
|
||||
return DurationUnit.Quarter;
|
||||
case 'y':
|
||||
return DurationUnit.Year;
|
||||
default:
|
||||
throw new Error('invalid duration unit');
|
||||
}
|
||||
|
@ -94,10 +88,6 @@ const toMomentUnitOfTime = (unit: DurationUnit): moment.unitOfTime.Diff => {
|
|||
return 'weeks';
|
||||
case DurationUnit.Month:
|
||||
return 'months';
|
||||
case DurationUnit.Quarter:
|
||||
return 'quarters';
|
||||
case DurationUnit.Year:
|
||||
return 'years';
|
||||
default:
|
||||
assertNever(unit);
|
||||
}
|
||||
|
|
|
@ -43,8 +43,6 @@ const summarySchema = t.type({
|
|||
errorBudget: errorBudgetSchema,
|
||||
});
|
||||
|
||||
type SummarySchema = t.TypeOf<typeof summarySchema>;
|
||||
|
||||
const historicalSummarySchema = t.intersection([
|
||||
t.type({
|
||||
date: dateType,
|
||||
|
@ -59,8 +57,6 @@ const previewDataSchema = t.type({
|
|||
|
||||
const dateRangeSchema = t.type({ from: dateType, to: dateType });
|
||||
|
||||
export type { SummarySchema };
|
||||
|
||||
export {
|
||||
ALL_VALUE,
|
||||
allOrAnyString,
|
||||
|
|
|
@ -11,7 +11,7 @@ import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-sc
|
|||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { toMomentUnitOfTime } from '../../../../utils/slo/duration';
|
||||
import { toCalendarAlignedMomentUnitOfTime } from '../../../../utils/slo/duration';
|
||||
import { toDurationLabel } from '../../../../utils/slo/labels';
|
||||
|
||||
export interface Props {
|
||||
|
@ -34,11 +34,11 @@ export function SloTimeWindowBadge({ slo }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
const unitMoment = toMomentUnitOfTime(unit);
|
||||
const unitMoment = toCalendarAlignedMomentUnitOfTime(unit);
|
||||
const now = moment.utc();
|
||||
|
||||
const periodStart = now.clone().startOf(unitMoment!).add(1, 'day');
|
||||
const periodEnd = now.clone().endOf(unitMoment!).add(1, 'day');
|
||||
const periodStart = now.clone().startOf(unitMoment);
|
||||
const periodEnd = now.clone().endOf(unitMoment);
|
||||
|
||||
const totalDurationInDays = periodEnd.diff(periodStart, 'days') + 1;
|
||||
const elapsedDurationInDays = now.diff(periodStart, 'days') + 1;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
|
||||
|
||||
type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M' | 'Y';
|
||||
type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M';
|
||||
|
||||
interface Duration {
|
||||
value: number;
|
||||
|
|
|
@ -28,24 +28,17 @@ export function toMinutes(duration: Duration) {
|
|||
return duration.value * 7 * 24 * 60;
|
||||
case 'M':
|
||||
return duration.value * 30 * 24 * 60;
|
||||
case 'Y':
|
||||
return duration.value * 365 * 24 * 60;
|
||||
default:
|
||||
assertNever(duration.unit);
|
||||
}
|
||||
|
||||
assertNever(duration.unit);
|
||||
}
|
||||
|
||||
export function toMomentUnitOfTime(unit: string): moment.unitOfTime.Diff | undefined {
|
||||
export function toCalendarAlignedMomentUnitOfTime(unit: string): moment.unitOfTime.StartOf {
|
||||
switch (unit) {
|
||||
case 'd':
|
||||
return 'days';
|
||||
default:
|
||||
case 'w':
|
||||
return 'weeks';
|
||||
return 'isoWeek';
|
||||
case 'M':
|
||||
return 'months';
|
||||
case 'Q':
|
||||
return 'quarters';
|
||||
case 'Y':
|
||||
return 'years';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,13 +112,6 @@ export function toDurationLabel(durationStr: string): string {
|
|||
duration: duration.value,
|
||||
},
|
||||
});
|
||||
case 'Y':
|
||||
return i18n.translate('xpack.observability.slo.duration.year', {
|
||||
defaultMessage: '{duration, plural, one {1 year} other {# years}}',
|
||||
values: {
|
||||
duration: duration.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,9 +139,5 @@ export function toDurationAdverbLabel(durationStr: string): string {
|
|||
return i18n.translate('xpack.observability.slo.duration.monthly', {
|
||||
defaultMessage: 'Monthly',
|
||||
});
|
||||
case 'Y':
|
||||
return i18n.translate('xpack.observability.slo.duration.yearly', {
|
||||
defaultMessage: 'Yearly',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,42 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
calendarAlignedTimeWindowSchema,
|
||||
rollingTimeWindowSchema,
|
||||
timeWindowSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import * as t from 'io-ts';
|
||||
import { rollingTimeWindowSchema, timeWindowSchema } from '@kbn/slo-schema';
|
||||
|
||||
type TimeWindow = t.TypeOf<typeof timeWindowSchema>;
|
||||
type RollingTimeWindow = t.TypeOf<typeof rollingTimeWindowSchema>;
|
||||
type CalendarAlignedTimeWindow = t.TypeOf<typeof calendarAlignedTimeWindowSchema>;
|
||||
|
||||
export type { RollingTimeWindow, TimeWindow };
|
||||
export type { RollingTimeWindow, TimeWindow, CalendarAlignedTimeWindow };
|
||||
|
||||
export function toCalendarAlignedTimeWindowMomentUnit(
|
||||
timeWindow: CalendarAlignedTimeWindow
|
||||
): moment.unitOfTime.StartOf {
|
||||
const unit = timeWindow.duration.unit;
|
||||
switch (unit) {
|
||||
case 'w':
|
||||
return 'isoWeeks';
|
||||
case 'M':
|
||||
return 'months';
|
||||
default:
|
||||
throw new Error(`Invalid calendar aligned time window duration unit: ${unit}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function toRollingTimeWindowMomentUnit(
|
||||
timeWindow: RollingTimeWindow
|
||||
): moment.unitOfTime.Diff {
|
||||
const unit = timeWindow.duration.unit;
|
||||
switch (unit) {
|
||||
case 'd':
|
||||
return 'days';
|
||||
default:
|
||||
throw new Error(`Invalid rolling time window duration unit: ${unit}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { computeBurnRate } from './compute_burn_rate';
|
||||
import { toDateRange } from './date_range';
|
||||
import { createSLO } from '../../services/slo/fixtures/slo';
|
||||
import { sixHoursRolling } from '../../services/slo/fixtures/time_window';
|
||||
import { ninetyDaysRolling } from '../../services/slo/fixtures/time_window';
|
||||
|
||||
describe('computeBurnRate', () => {
|
||||
it('computes 0 when total is 0', () => {
|
||||
|
@ -16,7 +16,7 @@ describe('computeBurnRate', () => {
|
|||
computeBurnRate(createSLO(), {
|
||||
good: 10,
|
||||
total: 0,
|
||||
dateRange: toDateRange(sixHoursRolling()),
|
||||
dateRange: toDateRange(ninetyDaysRolling()),
|
||||
})
|
||||
).toEqual(0);
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ describe('computeBurnRate', () => {
|
|||
computeBurnRate(createSLO(), {
|
||||
good: 9999,
|
||||
total: 1,
|
||||
dateRange: toDateRange(sixHoursRolling()),
|
||||
dateRange: toDateRange(ninetyDaysRolling()),
|
||||
})
|
||||
).toEqual(0);
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ describe('computeBurnRate', () => {
|
|||
computeBurnRate(createSLO({ objective: { target: 0.9 } }), {
|
||||
good: 90,
|
||||
total: 100,
|
||||
dateRange: toDateRange(sixHoursRolling()),
|
||||
dateRange: toDateRange(ninetyDaysRolling()),
|
||||
})
|
||||
).toEqual(1);
|
||||
});
|
||||
|
@ -46,7 +46,7 @@ describe('computeBurnRate', () => {
|
|||
computeBurnRate(createSLO({ objective: { target: 0.99 } }), {
|
||||
good: 90,
|
||||
total: 100,
|
||||
dateRange: toDateRange(sixHoursRolling()),
|
||||
dateRange: toDateRange(ninetyDaysRolling()),
|
||||
})
|
||||
).toEqual(10);
|
||||
});
|
||||
|
@ -56,7 +56,7 @@ describe('computeBurnRate', () => {
|
|||
computeBurnRate(createSLO({ objective: { target: 0.8 } }), {
|
||||
good: 90,
|
||||
total: 100,
|
||||
dateRange: toDateRange(sixHoursRolling()),
|
||||
dateRange: toDateRange(ninetyDaysRolling()),
|
||||
})
|
||||
).toEqual(0.5);
|
||||
});
|
||||
|
|
|
@ -5,17 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TimeWindow } from '../models/time_window';
|
||||
import { Duration } from '../models';
|
||||
import {
|
||||
monthlyCalendarAligned,
|
||||
ninetyDaysRolling,
|
||||
sevenDaysRolling,
|
||||
thirtyDaysRolling,
|
||||
weeklyCalendarAligned,
|
||||
} from '../../services/slo/fixtures/time_window';
|
||||
import { toDateRange } from './date_range';
|
||||
import { oneMonth, oneQuarter, oneWeek, thirtyDays } from '../../services/slo/fixtures/duration';
|
||||
|
||||
const NOW = new Date('2022-08-11T08:31:00.000Z');
|
||||
|
||||
describe('toDateRange', () => {
|
||||
describe('for calendar aligned time window', () => {
|
||||
it('computes the date range for weekly calendar', () => {
|
||||
const timeWindow = aCalendarTimeWindow(oneWeek());
|
||||
const timeWindow = weeklyCalendarAligned();
|
||||
expect(toDateRange(timeWindow, NOW)).toEqual({
|
||||
from: new Date('2022-08-08T00:00:00.000Z'),
|
||||
to: new Date('2022-08-14T23:59:59.999Z'),
|
||||
|
@ -23,7 +27,7 @@ describe('toDateRange', () => {
|
|||
});
|
||||
|
||||
it('computes the date range for monthly calendar', () => {
|
||||
const timeWindow = aCalendarTimeWindow(oneMonth());
|
||||
const timeWindow = monthlyCalendarAligned();
|
||||
expect(toDateRange(timeWindow, NOW)).toEqual({
|
||||
from: new Date('2022-08-01T00:00:00.000Z'),
|
||||
to: new Date('2022-08-31T23:59:59.999Z'),
|
||||
|
@ -33,42 +37,24 @@ describe('toDateRange', () => {
|
|||
|
||||
describe('for rolling time window', () => {
|
||||
it("computes the date range using a '30days' rolling window", () => {
|
||||
expect(toDateRange(aRollingTimeWindow(thirtyDays()), NOW)).toEqual({
|
||||
expect(toDateRange(thirtyDaysRolling(), NOW)).toEqual({
|
||||
from: new Date('2022-07-12T08:31:00.000Z'),
|
||||
to: new Date('2022-08-11T08:31:00.000Z'),
|
||||
});
|
||||
});
|
||||
|
||||
it("computes the date range using a 'weekly' rolling window", () => {
|
||||
expect(toDateRange(aRollingTimeWindow(oneWeek()), NOW)).toEqual({
|
||||
it("computes the date range using a '7days' rolling window", () => {
|
||||
expect(toDateRange(sevenDaysRolling(), NOW)).toEqual({
|
||||
from: new Date('2022-08-04T08:31:00.000Z'),
|
||||
to: new Date('2022-08-11T08:31:00.000Z'),
|
||||
});
|
||||
});
|
||||
|
||||
it("computes the date range using a 'monthly' rolling window", () => {
|
||||
expect(toDateRange(aRollingTimeWindow(oneMonth()), NOW)).toEqual({
|
||||
from: new Date('2022-07-11T08:31:00.000Z'),
|
||||
to: new Date('2022-08-11T08:31:00.000Z'),
|
||||
});
|
||||
});
|
||||
|
||||
it("computes the date range using a 'quarterly' rolling window", () => {
|
||||
expect(toDateRange(aRollingTimeWindow(oneQuarter()), NOW)).toEqual({
|
||||
from: new Date('2022-05-11T08:31:00.000Z'),
|
||||
it("computes the date range using a '90days' rolling window", () => {
|
||||
expect(toDateRange(ninetyDaysRolling(), NOW)).toEqual({
|
||||
from: new Date('2022-05-13T08:31:00.000Z'),
|
||||
to: new Date('2022-08-11T08:31:00.000Z'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function aCalendarTimeWindow(duration: Duration): TimeWindow {
|
||||
return {
|
||||
duration,
|
||||
type: 'calendarAligned',
|
||||
};
|
||||
}
|
||||
|
||||
function aRollingTimeWindow(duration: Duration): TimeWindow {
|
||||
return { duration, type: 'rolling' };
|
||||
}
|
||||
|
|
|
@ -5,24 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { calendarAlignedTimeWindowSchema, rollingTimeWindowSchema } from '@kbn/slo-schema';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import moment from 'moment';
|
||||
import { calendarAlignedTimeWindowSchema, rollingTimeWindowSchema } from '@kbn/slo-schema';
|
||||
|
||||
import { DateRange, toMomentUnitOfTime } from '../models';
|
||||
import type { TimeWindow } from '../models/time_window';
|
||||
import { DateRange } from '../models';
|
||||
import {
|
||||
TimeWindow,
|
||||
toCalendarAlignedTimeWindowMomentUnit,
|
||||
toRollingTimeWindowMomentUnit,
|
||||
} from '../models/time_window';
|
||||
|
||||
export const toDateRange = (timeWindow: TimeWindow, currentDate: Date = new Date()): DateRange => {
|
||||
if (calendarAlignedTimeWindowSchema.is(timeWindow)) {
|
||||
const unit = toMomentUnitOfTime(timeWindow.duration.unit);
|
||||
if (unit === 'weeks') {
|
||||
// moment startOf(week) returns sunday, but we want to stay consistent with es "now/w" date math which returns monday.
|
||||
const from = moment.utc(currentDate).startOf(unit).add(1, 'day');
|
||||
const to = moment.utc(currentDate).endOf(unit).add(1, 'day');
|
||||
|
||||
return { from: from.toDate(), to: to.toDate() };
|
||||
}
|
||||
|
||||
const unit = toCalendarAlignedTimeWindowMomentUnit(timeWindow);
|
||||
const from = moment.utc(currentDate).startOf(unit);
|
||||
const to = moment.utc(currentDate).endOf(unit);
|
||||
|
||||
|
@ -30,12 +25,14 @@ export const toDateRange = (timeWindow: TimeWindow, currentDate: Date = new Date
|
|||
}
|
||||
|
||||
if (rollingTimeWindowSchema.is(timeWindow)) {
|
||||
const unit = toMomentUnitOfTime(timeWindow.duration.unit);
|
||||
const unit = toRollingTimeWindowMomentUnit(timeWindow);
|
||||
const now = moment.utc(currentDate).startOf('minute');
|
||||
const from = now.clone().subtract(timeWindow.duration.value, unit);
|
||||
const to = now.clone();
|
||||
|
||||
return {
|
||||
from: now.clone().subtract(timeWindow.duration.value, unit).toDate(),
|
||||
to: now.toDate(),
|
||||
from: from.toDate(),
|
||||
to: to.toDate(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { validateSLO } from '.';
|
||||
import { oneMinute, sixHours } from '../../services/slo/fixtures/duration';
|
||||
import { createSLO } from '../../services/slo/fixtures/slo';
|
||||
import { sevenDaysRolling } from '../../services/slo/fixtures/time_window';
|
||||
import { Duration, DurationUnit } from '../models';
|
||||
|
||||
describe('validateSLO', () => {
|
||||
|
@ -41,16 +42,12 @@ describe('validateSLO', () => {
|
|||
{ duration: new Duration(2, DurationUnit.Hour), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Day), shouldThrow: true },
|
||||
{ duration: new Duration(7, DurationUnit.Day), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Week), shouldThrow: false },
|
||||
{ duration: new Duration(2, DurationUnit.Week), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Month), shouldThrow: false },
|
||||
{ duration: new Duration(2, DurationUnit.Month), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Quarter), shouldThrow: true },
|
||||
{ duration: new Duration(3, DurationUnit.Quarter), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Year), shouldThrow: true },
|
||||
{ duration: new Duration(3, DurationUnit.Year), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Week), shouldThrow: false },
|
||||
{ duration: new Duration(1, DurationUnit.Month), shouldThrow: false },
|
||||
])(
|
||||
'throws when time window calendar aligned is not 1 week or 1 month',
|
||||
'throws when calendar aligned time window is not 1 week or 1 month',
|
||||
({ duration, shouldThrow }) => {
|
||||
if (shouldThrow) {
|
||||
expect(() =>
|
||||
|
@ -72,6 +69,34 @@ describe('validateSLO', () => {
|
|||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ duration: new Duration(7, DurationUnit.Day), shouldThrow: false },
|
||||
{ duration: new Duration(30, DurationUnit.Day), shouldThrow: false },
|
||||
{ duration: new Duration(90, DurationUnit.Day), shouldThrow: false },
|
||||
{ duration: new Duration(1, DurationUnit.Hour), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Day), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Week), shouldThrow: true },
|
||||
{ duration: new Duration(1, DurationUnit.Month), shouldThrow: true },
|
||||
])('throws when rolling time window is not 7, 30 or 90days', ({ duration, shouldThrow }) => {
|
||||
if (shouldThrow) {
|
||||
expect(() =>
|
||||
validateSLO(
|
||||
createSLO({
|
||||
timeWindow: { duration, type: 'rolling' },
|
||||
})
|
||||
)
|
||||
).toThrowError('Invalid time_window.duration');
|
||||
} else {
|
||||
expect(() =>
|
||||
validateSLO(
|
||||
createSLO({
|
||||
timeWindow: { duration, type: 'rolling' },
|
||||
})
|
||||
)
|
||||
).not.toThrowError();
|
||||
}
|
||||
});
|
||||
|
||||
describe('settings', () => {
|
||||
it("throws when frequency is longer or equal than '1h'", () => {
|
||||
const slo = createSLO({
|
||||
|
@ -173,25 +198,11 @@ describe('validateSLO', () => {
|
|||
objective: { ...slo.objective, timesliceWindow: new Duration(1, DurationUnit.Month) },
|
||||
})
|
||||
).toThrowError('Invalid objective.timeslice_window');
|
||||
|
||||
expect(() =>
|
||||
validateSLO({
|
||||
...slo,
|
||||
objective: { ...slo.objective, timesliceWindow: new Duration(1, DurationUnit.Quarter) },
|
||||
})
|
||||
).toThrowError('Invalid objective.timeslice_window');
|
||||
|
||||
expect(() =>
|
||||
validateSLO({
|
||||
...slo,
|
||||
objective: { ...slo.objective, timesliceWindow: new Duration(1, DurationUnit.Year) },
|
||||
})
|
||||
).toThrowError('Invalid objective.timeslice_window');
|
||||
});
|
||||
|
||||
it("throws when 'objective.timeslice_window' is longer than 'slo.time_window'", () => {
|
||||
const slo = createSLO({
|
||||
timeWindow: { duration: new Duration(1, DurationUnit.Week), type: 'rolling' },
|
||||
timeWindow: sevenDaysRolling(),
|
||||
budgetingMethod: 'timeslices',
|
||||
objective: {
|
||||
target: 0.95,
|
||||
|
|
|
@ -85,13 +85,8 @@ function isValidTargetNumber(value: number): boolean {
|
|||
}
|
||||
|
||||
function isValidRollingTimeWindowDuration(duration: Duration): boolean {
|
||||
return [
|
||||
DurationUnit.Day,
|
||||
DurationUnit.Week,
|
||||
DurationUnit.Month,
|
||||
DurationUnit.Quarter,
|
||||
DurationUnit.Year,
|
||||
].includes(duration.unit);
|
||||
// 7, 30 or 90days accepted
|
||||
return duration.unit === DurationUnit.Day && [7, 30, 90].includes(duration.value);
|
||||
}
|
||||
|
||||
function isValidCalendarAlignedTimeWindowDuration(duration: Duration): boolean {
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import { Duration, DurationUnit } from '../../../domain/models';
|
||||
|
||||
export function oneQuarter(): Duration {
|
||||
return new Duration(1, DurationUnit.Quarter);
|
||||
}
|
||||
|
||||
export function thirtyDays(): Duration {
|
||||
return new Duration(30, DurationUnit.Day);
|
||||
}
|
||||
|
||||
export function ninetyDays(): Duration {
|
||||
return new Duration(90, DurationUnit.Day);
|
||||
}
|
||||
|
||||
export function oneMonth(): Duration {
|
||||
return new Duration(1, DurationUnit.Month);
|
||||
}
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import { CreateSLOParams, HistogramIndicator, sloSchema } from '@kbn/slo-schema';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { v1 as uuidv1 } from 'uuid';
|
||||
import { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import { sloSchema, CreateSLOParams, HistogramIndicator } from '@kbn/slo-schema';
|
||||
|
||||
import { SO_SLO_TYPE } from '../../../saved_objects';
|
||||
import {
|
||||
APMTransactionDurationIndicator,
|
||||
APMTransactionErrorRateIndicator,
|
||||
|
@ -22,9 +20,10 @@ import {
|
|||
SLO,
|
||||
StoredSLO,
|
||||
} from '../../../domain/models';
|
||||
import { SO_SLO_TYPE } from '../../../saved_objects';
|
||||
import { Paginated } from '../slo_repository';
|
||||
import { oneWeek, twoMinute } from './duration';
|
||||
import { sevenDaysRolling } from './time_window';
|
||||
import { twoMinute } from './duration';
|
||||
import { sevenDaysRolling, weeklyCalendarAligned } from './time_window';
|
||||
|
||||
export const createAPMTransactionErrorRateIndicator = (
|
||||
params: Partial<APMTransactionErrorRateIndicator['params']> = {}
|
||||
|
@ -184,10 +183,7 @@ export const createSLOWithTimeslicesBudgetingMethod = (params: Partial<SLO> = {}
|
|||
|
||||
export const createSLOWithCalendarTimeWindow = (params: Partial<SLO> = {}): SLO => {
|
||||
return createSLO({
|
||||
timeWindow: {
|
||||
duration: oneWeek(),
|
||||
type: 'calendarAligned',
|
||||
},
|
||||
timeWindow: weeklyCalendarAligned(),
|
||||
...params,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,15 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RollingTimeWindow, TimeWindow } from '../../../domain/models/time_window';
|
||||
import { oneWeek, sevenDays, sixHours, thirtyDays } from './duration';
|
||||
|
||||
export function sixHoursRolling(): TimeWindow {
|
||||
return {
|
||||
duration: sixHours(),
|
||||
type: 'rolling',
|
||||
};
|
||||
}
|
||||
import {
|
||||
CalendarAlignedTimeWindow,
|
||||
RollingTimeWindow,
|
||||
TimeWindow,
|
||||
} from '../../../domain/models/time_window';
|
||||
import { ninetyDays, oneMonth, oneWeek, sevenDays, thirtyDays } from './duration';
|
||||
|
||||
export function sevenDaysRolling(): RollingTimeWindow {
|
||||
return {
|
||||
|
@ -28,9 +25,23 @@ export function thirtyDaysRolling(): RollingTimeWindow {
|
|||
};
|
||||
}
|
||||
|
||||
export function weeklyCalendarAligned(): TimeWindow {
|
||||
export function ninetyDaysRolling(): TimeWindow {
|
||||
return {
|
||||
duration: ninetyDays(),
|
||||
type: 'rolling',
|
||||
};
|
||||
}
|
||||
|
||||
export function weeklyCalendarAligned(): CalendarAlignedTimeWindow {
|
||||
return {
|
||||
duration: oneWeek(),
|
||||
type: 'calendarAligned',
|
||||
};
|
||||
}
|
||||
|
||||
export function monthlyCalendarAligned(): CalendarAlignedTimeWindow {
|
||||
return {
|
||||
duration: oneMonth(),
|
||||
type: 'calendarAligned',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,11 +13,15 @@ import {
|
|||
MsearchMultisearchBody,
|
||||
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema';
|
||||
import {
|
||||
occurrencesBudgetingMethodSchema,
|
||||
timeslicesBudgetingMethodSchema,
|
||||
toMomentUnitOfTime,
|
||||
} from '@kbn/slo-schema';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import moment from 'moment';
|
||||
import { SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants';
|
||||
import { DateRange, Duration, IndicatorData, SLO } from '../../domain/models';
|
||||
import { toDateRange } from '../../domain/services/date_range';
|
||||
import { InternalQueryError } from '../../errors';
|
||||
|
||||
export interface SLIClient {
|
||||
|
@ -47,10 +51,7 @@ export class DefaultSLIClient implements SLIClient {
|
|||
a.duration.isShorterThan(b.duration) ? 1 : -1
|
||||
);
|
||||
const longestLookbackWindow = sortedLookbackWindows[0];
|
||||
const longestDateRange = toDateRange({
|
||||
duration: longestLookbackWindow.duration,
|
||||
type: 'rolling',
|
||||
});
|
||||
const longestDateRange = getLookbackDateRange(longestLookbackWindow.duration);
|
||||
|
||||
if (occurrencesBudgetingMethodSchema.is(slo.budgetingMethod)) {
|
||||
const result = await this.esClient.search<unknown, EsAggregations>({
|
||||
|
@ -179,3 +180,15 @@ function handleWindowedResult(
|
|||
|
||||
return indicatorDataPerLookbackWindow;
|
||||
}
|
||||
|
||||
function getLookbackDateRange(duration: Duration): { from: Date; to: Date } {
|
||||
const unit = toMomentUnitOfTime(duration.unit);
|
||||
const now = moment.utc().startOf('minute');
|
||||
const from = now.clone().subtract(duration.value, unit);
|
||||
const to = now.clone();
|
||||
|
||||
return {
|
||||
from: from.toDate(),
|
||||
to: to.toDate(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -27321,7 +27321,6 @@
|
|||
"xpack.observability.slo.duration.minute": "{duration, plural, one {1 minute} many {# minutes} other {# minutes}}",
|
||||
"xpack.observability.slo.duration.month": "{duration, plural, one {1 mois} many {# mois} other {# mois}}",
|
||||
"xpack.observability.slo.duration.week": "{duration, plural, one {1 semaine} many {# semaines} other {# semaines}}",
|
||||
"xpack.observability.slo.duration.year": "{duration, plural, one {1 an} many {# ans} other {# prochaines années}}",
|
||||
"xpack.observability.slo.indicatorTypeBadge.exploreInApm": "Afficher les détails de {service}",
|
||||
"xpack.observability.slo.list.sortByType": "Trier par {type}",
|
||||
"xpack.observability.slo.rules.burnRate.errors.invalidThresholdValue": "Le seuil du taux d'avancement doit être compris entre 1 et {maxBurnRate}.",
|
||||
|
|
|
@ -27321,7 +27321,6 @@
|
|||
"xpack.observability.slo.duration.minute": "{duration, plural, other {#分}}",
|
||||
"xpack.observability.slo.duration.month": "{duration, plural, other {#月}}",
|
||||
"xpack.observability.slo.duration.week": "{duration, plural, other {#週}}",
|
||||
"xpack.observability.slo.duration.year": "{duration, plural, other {#年}}",
|
||||
"xpack.observability.slo.indicatorTypeBadge.exploreInApm": "{service}詳細を表示",
|
||||
"xpack.observability.slo.list.sortByType": "{type}で並べ替え",
|
||||
"xpack.observability.slo.rules.burnRate.errors.invalidThresholdValue": "バーンレートしきい値は1以上{maxBurnRate}以下でなければなりません。",
|
||||
|
|
|
@ -27319,7 +27319,6 @@
|
|||
"xpack.observability.slo.duration.minute": "{duration, plural, other {# 分钟}}",
|
||||
"xpack.observability.slo.duration.month": "{duration, plural, other {# 个月}}",
|
||||
"xpack.observability.slo.duration.week": "{duration, plural, other {# 周}}",
|
||||
"xpack.observability.slo.duration.year": "{duration, plural, other {# 年}}",
|
||||
"xpack.observability.slo.indicatorTypeBadge.exploreInApm": "查看 {service} 详情",
|
||||
"xpack.observability.slo.list.sortByType": "按 {type} 排序",
|
||||
"xpack.observability.slo.rules.burnRate.errors.invalidThresholdValue": "消耗速度阈值必须介于 1 和 {maxBurnRate} 之间。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue