Improve time window handling and validation (#161978)

This commit is contained in:
Kevin Delemme 2023-07-14 15:40:50 -04:00
parent 8721978c5b
commit d9f098f210
20 changed files with 165 additions and 169 deletions

View file

@ -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);
});
});

View file

@ -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);
}

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -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';
}
}

View file

@ -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',
});
}
}

View file

@ -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}`);
}
}

View file

@ -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);
});

View file

@ -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' };
}

View file

@ -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(),
};
}

View file

@ -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,

View file

@ -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 {

View file

@ -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);
}

View file

@ -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,
});
};

View file

@ -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',
};
}

View file

@ -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(),
};
}

View file

@ -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}.",

View file

@ -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}以下でなければなりません。",

View file

@ -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} 之间。",