mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
feat(slo): prevent initial backfill (#184312)
This commit is contained in:
parent
5bf276d21f
commit
5a2f1ac5ba
43 changed files with 472 additions and 69 deletions
|
@ -29,6 +29,7 @@ const objectiveSchema = t.intersection([
|
|||
const settingsSchema = t.type({
|
||||
syncDelay: durationType,
|
||||
frequency: durationType,
|
||||
preventInitialBackfill: t.boolean,
|
||||
});
|
||||
|
||||
const groupBySchema = allOrAnyStringOrArray;
|
||||
|
|
|
@ -190,6 +190,14 @@
|
|||
"default": "asc"
|
||||
},
|
||||
"example": "asc"
|
||||
},
|
||||
{
|
||||
"name": "hideStale",
|
||||
"in": "query",
|
||||
"description": "Hide stale SLOs from the list as defined by stale SLO threshold in SLO settings",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -1768,12 +1776,20 @@
|
|||
"syncDelay": {
|
||||
"description": "The synch delay to apply to the transform. Default 1m",
|
||||
"type": "string",
|
||||
"default": "1m",
|
||||
"example": "5m"
|
||||
},
|
||||
"frequency": {
|
||||
"description": "Configure how often the transform runs, default 1m",
|
||||
"type": "string",
|
||||
"default": "1m",
|
||||
"example": "5m"
|
||||
},
|
||||
"preventInitialBackfill": {
|
||||
"description": "Prevents the transform from backfilling data when it starts.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -121,6 +121,11 @@ paths:
|
|||
- desc
|
||||
default: asc
|
||||
example: asc
|
||||
- name: hideStale
|
||||
in: query
|
||||
description: Hide stale SLOs from the list as defined by stale SLO threshold in SLO settings
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: Successful request
|
||||
|
@ -1210,11 +1215,18 @@ components:
|
|||
syncDelay:
|
||||
description: The synch delay to apply to the transform. Default 1m
|
||||
type: string
|
||||
default: 1m
|
||||
example: 5m
|
||||
frequency:
|
||||
description: Configure how often the transform runs, default 1m
|
||||
type: string
|
||||
default: 1m
|
||||
example: 5m
|
||||
preventInitialBackfill:
|
||||
description: Prevents the transform from backfilling data when it starts.
|
||||
type: boolean
|
||||
default: false
|
||||
example: true
|
||||
summary_status:
|
||||
title: summary status
|
||||
type: string
|
||||
|
|
|
@ -5,8 +5,15 @@ properties:
|
|||
syncDelay:
|
||||
description: The synch delay to apply to the transform. Default 1m
|
||||
type: string
|
||||
default: 1m
|
||||
example: 5m
|
||||
frequency:
|
||||
description: Configure how often the transform runs, default 1m
|
||||
type: string
|
||||
default: 1m
|
||||
example: 5m
|
||||
preventInitialBackfill:
|
||||
description: Prevents the transform from backfilling data when it starts.
|
||||
type: boolean
|
||||
default: false
|
||||
example: true
|
||||
|
|
|
@ -51,6 +51,7 @@ const baseSlo: Omit<SLOWithSummaryResponse, 'id'> = {
|
|||
settings: {
|
||||
syncDelay: '1m',
|
||||
frequency: '1m',
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
summary: {
|
||||
status: 'HEALTHY',
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('SloEditLocator', () => {
|
|||
it('should return correct url when slo is provided', async () => {
|
||||
const location = await locator.getLocation(buildSlo({ id: 'foo' }));
|
||||
expect(location.path).toEqual(
|
||||
"/edit/foo?_a=(budgetingMethod:occurrences,createdAt:'2022-12-29T10:11:12.000Z',description:'some%20description%20useful',enabled:!t,groupBy:'*',groupings:(),id:foo,indicator:(params:(filter:'baz:%20foo%20and%20bar%20%3E%202',good:'http_status:%202xx',index:some-index,timestampField:custom_timestamp,total:'a%20query'),type:sli.kql.custom),instanceId:'*',meta:(),name:'super%20important%20level%20service',objective:(target:0.98),revision:1,settings:(frequency:'1m',syncDelay:'1m'),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:'30d',type:rolling),updatedAt:'2022-12-29T10:11:12.000Z',version:2)"
|
||||
"/edit/foo?_a=(budgetingMethod:occurrences,createdAt:'2022-12-29T10:11:12.000Z',description:'some%20description%20useful',enabled:!t,groupBy:'*',groupings:(),id:foo,indicator:(params:(filter:'baz:%20foo%20and%20bar%20%3E%202',good:'http_status:%202xx',index:some-index,timestampField:custom_timestamp,total:'a%20query'),type:sli.kql.custom),instanceId:'*',meta:(),name:'super%20important%20level%20service',objective:(target:0.98),revision:1,settings:(frequency:'1m',preventInitialBackfill:!f,syncDelay:'1m'),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:'30d',type:rolling),updatedAt:'2022-12-29T10:11:12.000Z',version:2)"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiCheckbox,
|
||||
EuiFieldNumber,
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
|
@ -18,10 +19,10 @@ import {
|
|||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { TimeWindowType } from '@kbn/slo-schema';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
BUDGETING_METHOD_OPTIONS,
|
||||
CALENDARALIGNED_TIMEWINDOW_OPTIONS,
|
||||
|
@ -43,6 +44,7 @@ export function SloEditFormObjectiveSection() {
|
|||
const budgetingSelect = useGeneratedHtmlId({ prefix: 'budgetingSelect' });
|
||||
const timeWindowTypeSelect = useGeneratedHtmlId({ prefix: 'timeWindowTypeSelect' });
|
||||
const timeWindowSelect = useGeneratedHtmlId({ prefix: 'timeWindowSelect' });
|
||||
const preventBackfillCheckbox = useGeneratedHtmlId({ prefix: 'preventBackfill' });
|
||||
const timeWindowType = watch('timeWindow.type');
|
||||
const indicator = watch('indicator.type');
|
||||
|
||||
|
@ -283,6 +285,43 @@ export function SloEditFormObjectiveSection() {
|
|||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGrid columns={3}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow isInvalid={getFieldState('settings.preventInitialBackfill').invalid}>
|
||||
<Controller
|
||||
name="settings.preventInitialBackfill"
|
||||
control={control}
|
||||
render={({ field: { ref, onChange, ...field } }) => (
|
||||
<EuiCheckbox
|
||||
id={preventBackfillCheckbox}
|
||||
label={
|
||||
<span>
|
||||
{i18n.translate('xpack.slo.sloEdit.settings.preventInitialBackfill.label', {
|
||||
defaultMessage: 'Prevent initial backfill of data',
|
||||
})}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.slo.sloEdit.settings.preventInitialBackfill.tooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'Start aggregating data from the time the SLO is created, instead of backfilling data from the beginning of the time window.',
|
||||
}
|
||||
)}
|
||||
position="top"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
checked={Boolean(field.value)}
|
||||
onChange={(event: any) => onChange(event.target.checked)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -219,6 +219,9 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES: CreateSLOForm = {
|
|||
target: 99,
|
||||
},
|
||||
groupBy: ALL_VALUE,
|
||||
settings: {
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const SLO_EDIT_FORM_DEFAULT_VALUES_CUSTOM_METRIC: CreateSLOForm = {
|
||||
|
@ -235,6 +238,9 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES_CUSTOM_METRIC: CreateSLOForm = {
|
|||
target: 99,
|
||||
},
|
||||
groupBy: ALL_VALUE,
|
||||
settings: {
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const SLO_EDIT_FORM_DEFAULT_VALUES_SYNTHETICS_AVAILABILITY: CreateSLOForm = {
|
||||
|
@ -251,6 +257,9 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES_SYNTHETICS_AVAILABILITY: CreateSLOForm
|
|||
target: 99,
|
||||
},
|
||||
groupBy: SYNTHETICS_DEFAULT_GROUPINGS,
|
||||
settings: {
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const COMPARATOR_GT = i18n.translate('xpack.slo.sloEdit.sliType.timesliceMetric.gtLabel', {
|
||||
|
|
|
@ -25,6 +25,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -70,6 +73,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -97,6 +103,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -136,6 +145,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -165,6 +177,9 @@ Object {
|
|||
"timesliceTarget": 95,
|
||||
"timesliceWindow": "2",
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -192,6 +207,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "1M",
|
||||
|
@ -220,6 +238,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -249,6 +270,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -276,6 +300,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
@ -303,6 +330,9 @@ Object {
|
|||
"objective": Object {
|
||||
"target": 99,
|
||||
},
|
||||
"settings": Object {
|
||||
"preventInitialBackfill": false,
|
||||
},
|
||||
"tags": Array [],
|
||||
"timeWindow": Object {
|
||||
"duration": "30d",
|
||||
|
|
|
@ -50,6 +50,9 @@ export function transformSloResponseToCreateSloForm(
|
|||
},
|
||||
groupBy: [values.groupBy].flat(),
|
||||
tags: values.tags,
|
||||
settings: {
|
||||
preventInitialBackfill: values.settings?.preventInitialBackfill ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -76,6 +79,9 @@ export function transformCreateSLOFormToCreateSLOInput(values: CreateSLOForm): C
|
|||
},
|
||||
tags: values.tags,
|
||||
groupBy: [values.groupBy].flat(),
|
||||
settings: {
|
||||
preventInitialBackfill: values.settings?.preventInitialBackfill ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -102,6 +108,9 @@ export function transformValuesToUpdateSLOInput(values: CreateSLOForm): UpdateSL
|
|||
},
|
||||
tags: values.tags,
|
||||
groupBy: [values.groupBy].flat(),
|
||||
settings: {
|
||||
preventInitialBackfill: values.settings?.preventInitialBackfill ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -211,5 +220,9 @@ export function transformPartialUrlStateToFormState(
|
|||
state.timeWindow = { duration: values.timeWindow.duration, type: values.timeWindow.type };
|
||||
}
|
||||
|
||||
if (!!values.settings?.preventInitialBackfill) {
|
||||
state.settings = { preventInitialBackfill: values.settings.preventInitialBackfill };
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -23,4 +23,7 @@ export interface CreateSLOForm<IndicatorType = Indicator> {
|
|||
timesliceWindow?: string;
|
||||
};
|
||||
groupBy: string[] | string;
|
||||
settings: {
|
||||
preventInitialBackfill: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('remote SLO URLs Utils', () => {
|
|||
`"https://cloud.elast.co/app/slos/edit/fixed-id"`
|
||||
);
|
||||
expect(createRemoteSloCloneUrl(remoteSlo)).toMatchInlineSnapshot(
|
||||
`"https://cloud.elast.co/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"`
|
||||
`"https://cloud.elast.co/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,preventInitialBackfill:!f,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -71,7 +71,7 @@ describe('remote SLO URLs Utils', () => {
|
|||
`"https://cloud.elast.co/s/my-custom-space/app/slos/edit/fixed-id"`
|
||||
);
|
||||
expect(createRemoteSloCloneUrl(remoteSlo, 'my-custom-space')).toMatchInlineSnapshot(
|
||||
`"https://cloud.elast.co/s/my-custom-space/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"`
|
||||
`"https://cloud.elast.co/s/my-custom-space/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,preventInitialBackfill:!f,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -103,6 +103,7 @@ describe('validateSLO', () => {
|
|||
settings: {
|
||||
frequency: sixHours(),
|
||||
syncDelay: oneMinute(),
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
});
|
||||
expect(() => validateSLO(slo)).toThrowError('Invalid settings.frequency');
|
||||
|
@ -113,6 +114,7 @@ describe('validateSLO', () => {
|
|||
settings: {
|
||||
frequency: oneMinute(),
|
||||
syncDelay: sixHours(),
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
});
|
||||
expect(() => validateSLO(slo)).toThrowError('Invalid settings.sync_delay');
|
||||
|
|
|
@ -147,6 +147,7 @@ exports[`ResetSLO resets all associated resources 6`] = `
|
|||
"unit": "m",
|
||||
"value": 1,
|
||||
},
|
||||
"preventInitialBackfill": false,
|
||||
"syncDelay": Duration {
|
||||
"unit": "m",
|
||||
"value": 1,
|
||||
|
@ -410,6 +411,7 @@ exports[`ResetSLO resets all associated resources 9`] = `
|
|||
"unit": "m",
|
||||
"value": 1,
|
||||
},
|
||||
"preventInitialBackfill": false,
|
||||
"syncDelay": Duration {
|
||||
"unit": "m",
|
||||
"value": 1,
|
||||
|
|
|
@ -36,6 +36,7 @@ Object {
|
|||
"unit": "m",
|
||||
"value": 1,
|
||||
},
|
||||
"preventInitialBackfill": false,
|
||||
"syncDelay": Duration {
|
||||
"unit": "m",
|
||||
"value": 1,
|
||||
|
|
|
@ -68,6 +68,7 @@ describe('CreateSLO', () => {
|
|||
settings: {
|
||||
syncDelay: oneMinute(),
|
||||
frequency: oneMinute(),
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
revision: 1,
|
||||
tags: [],
|
||||
|
@ -108,6 +109,40 @@ describe('CreateSLO', () => {
|
|||
settings: {
|
||||
syncDelay: fiveMinute(),
|
||||
frequency: oneMinute(),
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
revision: 1,
|
||||
tags: ['one', 'two'],
|
||||
enabled: true,
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date),
|
||||
}),
|
||||
{ throwOnConflict: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('overrides the settings when provided', async () => {
|
||||
const sloParams = createSLOParams({
|
||||
indicator: createAPMTransactionErrorRateIndicator(),
|
||||
tags: ['one', 'two'],
|
||||
settings: {
|
||||
syncDelay: fiveMinute(),
|
||||
frequency: fiveMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
mockTransformManager.install.mockResolvedValue('slo-transform-id');
|
||||
|
||||
await createSLO.execute(sloParams);
|
||||
|
||||
expect(mockRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
...sloParams,
|
||||
id: expect.any(String),
|
||||
settings: {
|
||||
syncDelay: fiveMinute(),
|
||||
frequency: fiveMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
revision: 1,
|
||||
tags: ['one', 'two'],
|
||||
|
|
|
@ -127,6 +127,7 @@ export class CreateSLO {
|
|||
settings: {
|
||||
syncDelay: params.settings?.syncDelay ?? new Duration(1, DurationUnit.Minute),
|
||||
frequency: params.settings?.frequency ?? new Duration(1, DurationUnit.Minute),
|
||||
preventInitialBackfill: params.settings?.preventInitialBackfill ?? false,
|
||||
},
|
||||
revision: params.revision ?? 1,
|
||||
enabled: true,
|
||||
|
|
|
@ -80,6 +80,7 @@ describe('FindSLO', () => {
|
|||
settings: {
|
||||
syncDelay: '1m',
|
||||
frequency: '1m',
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
summary: {
|
||||
status: 'HEALTHY',
|
||||
|
|
|
@ -166,6 +166,7 @@ const defaultSLO: Omit<SLODefinition, 'id' | 'revision' | 'createdAt' | 'updated
|
|||
settings: {
|
||||
syncDelay: new Duration(1, DurationUnit.Minute),
|
||||
frequency: new Duration(1, DurationUnit.Minute),
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
tags: ['critical', 'k8s'],
|
||||
enabled: true,
|
||||
|
|
|
@ -80,6 +80,7 @@ describe('GetSLO', () => {
|
|||
settings: {
|
||||
syncDelay: '1m',
|
||||
frequency: '1m',
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
summary: {
|
||||
status: 'HEALTHY',
|
||||
|
|
|
@ -138,6 +138,11 @@ export class KibanaSavedObjectsSLORepository implements SLORepository {
|
|||
// if not present, we considered the version to be 1, e.g. not migrated.
|
||||
// We would need to call the _reset api on this SLO.
|
||||
version: storedSLO.version ?? 1,
|
||||
// settings.preventInitialBackfill was added in 8.15.0
|
||||
settings: {
|
||||
...storedSLO.settings,
|
||||
preventInitialBackfill: storedSLO.settings?.preventInitialBackfill ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
if (isLeft(result)) {
|
||||
|
|
|
@ -150,4 +150,28 @@ describe('APM Transaction Duration Transform Generator', () => {
|
|||
expect(transform.source.query).toMatchSnapshot();
|
||||
expect(transform.pivot?.group_by).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLO({
|
||||
indicator: createAPMTransactionDurationIndicator(),
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo);
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-300s/m', // 2m + 2m + 60s
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_tr
|
|||
import { APMTransactionDurationIndicator, SLODefinition } from '../../domain/models';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { parseIndex } from './common';
|
||||
import { getTimesliceTargetComparator } from './common';
|
||||
import { getTimesliceTargetComparator, getFilterRange } from './common';
|
||||
|
||||
export class ApmTransactionDurationTransformGenerator extends TransformGenerator {
|
||||
public getTransformParams(slo: SLODefinition): TransformPutTransformRequest {
|
||||
|
@ -71,15 +71,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
|
|||
}
|
||||
|
||||
private buildSource(slo: SLODefinition, indicator: APMTransactionDurationIndicator) {
|
||||
const queryFilter: estypes.QueryDslQueryContainer[] = [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const queryFilter: estypes.QueryDslQueryContainer[] = [getFilterRange(slo, '@timestamp')];
|
||||
|
||||
if (indicator.params.service !== ALL_VALUE) {
|
||||
queryFilter.push({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ALL_VALUE } from '@kbn/slo-schema';
|
||||
import { twoMinute } from '../fixtures/duration';
|
||||
import { oneMinute, twoMinute } from '../fixtures/duration';
|
||||
import {
|
||||
createAPMTransactionErrorRateIndicator,
|
||||
createSLO,
|
||||
|
@ -153,4 +153,28 @@ describe('APM Transaction Error Rate Transform Generator', () => {
|
|||
expect(transform.source.query).toMatchSnapshot();
|
||||
expect(transform.pivot?.group_by).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLO({
|
||||
indicator: createAPMTransactionErrorRateIndicator(),
|
||||
settings: {
|
||||
frequency: oneMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo);
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-240s/m', // 1m + 2m + 60s
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template';
|
||||
import { APMTransactionErrorRateIndicator, SLODefinition } from '../../domain/models';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { parseIndex } from './common';
|
||||
import { getTimesliceTargetComparator } from './common';
|
||||
import { parseIndex, getTimesliceTargetComparator, getFilterRange } from './common';
|
||||
|
||||
export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator {
|
||||
public getTransformParams(slo: SLODefinition): TransformPutTransformRequest {
|
||||
|
@ -70,15 +69,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
|
|||
}
|
||||
|
||||
private buildSource(slo: SLODefinition, indicator: APMTransactionErrorRateIndicator) {
|
||||
const queryFilter: estypes.QueryDslQueryContainer[] = [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const queryFilter: estypes.QueryDslQueryContainer[] = [getFilterRange(slo, '@timestamp')];
|
||||
|
||||
if (indicator.params.service !== ALL_VALUE) {
|
||||
queryFilter.push({
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getTimesliceTargetComparator, parseIndex } from './common';
|
||||
import { fiveMinute, twoMinute } from '../fixtures/duration';
|
||||
import { createSLO } from '../fixtures/slo';
|
||||
import { thirtyDaysRolling } from '../fixtures/time_window';
|
||||
import { getTimesliceTargetComparator, parseIndex, getFilterRange } from './common';
|
||||
|
||||
describe('common', () => {
|
||||
describe('parseIndex', () => {
|
||||
|
@ -30,4 +33,49 @@ describe('common', () => {
|
|||
expect(getTimesliceTargetComparator(0.000000001)).toBe('>=');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFilterRange', () => {
|
||||
it('starts at now (accounting for delay) when preventInitialBackfill is true', () => {
|
||||
expect(
|
||||
getFilterRange(
|
||||
createSLO({
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: fiveMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
}),
|
||||
'@timestamp'
|
||||
)
|
||||
).toEqual({
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-480s/m',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('starts at now minus the time window when preventInitialBackfill is false', () => {
|
||||
expect(
|
||||
getFilterRange(
|
||||
createSLO({
|
||||
timeWindow: thirtyDaysRolling(),
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: fiveMinute(),
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
}),
|
||||
'@timestamp'
|
||||
)
|
||||
).toEqual({
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-30d/d',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
|
||||
import { buildEsQuery, fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
import { QuerySchema, kqlQuerySchema } from '@kbn/slo-schema';
|
||||
import { kqlQuerySchema, QuerySchema } from '@kbn/slo-schema';
|
||||
import { SLODefinition } from '../../domain/models';
|
||||
import { getDelayInSecondsFromSLO } from '../../domain/services/get_delay_in_seconds_from_slo';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
|
||||
export function getElasticsearchQueryOrThrow(kuery: QuerySchema = '') {
|
||||
|
@ -39,3 +41,26 @@ export function parseIndex(index: string): string | string[] {
|
|||
export function getTimesliceTargetComparator(timesliceTarget: number) {
|
||||
return timesliceTarget === 0 ? '>' : '>=';
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the settings.preventInitialBackfill flag to determine the range filter for the rollup transform
|
||||
* preventInitialBackfill == true: we use the current time minus some buffer to account for the ingestion delay
|
||||
* preventInitialBackfill === false: we use the time window duration to get the data for the last N days
|
||||
*/
|
||||
export function getFilterRange(slo: SLODefinition, timestampField: string) {
|
||||
return slo.settings.preventInitialBackfill === true
|
||||
? {
|
||||
range: {
|
||||
[timestampField]: {
|
||||
gte: `now-${getDelayInSecondsFromSLO(slo)}s/m`,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
range: {
|
||||
[timestampField]: {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -165,4 +165,28 @@ describe('Histogram Transform Generator', () => {
|
|||
|
||||
expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLO({
|
||||
indicator: createHistogramIndicator(),
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo);
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
log_timestamp: {
|
||||
gte: 'now-300s/m', // 2m + 2m + 60s
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_tr
|
|||
import { SLODefinition } from '../../domain/models';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { GetHistogramIndicatorAggregation } from '../aggregations';
|
||||
import { getTimesliceTargetComparator } from './common';
|
||||
import { getTimesliceTargetComparator, getFilterRange } from './common';
|
||||
|
||||
export class HistogramTransformGenerator extends TransformGenerator {
|
||||
public getTransformParams(slo: SLODefinition): TransformPutTransformRequest {
|
||||
|
@ -52,13 +52,7 @@ export class HistogramTransformGenerator extends TransformGenerator {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
[indicator.params.timestampField]: {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
getFilterRange(slo, indicator.params.timestampField),
|
||||
getElasticsearchQueryOrThrow(indicator.params.filter),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -121,4 +121,28 @@ describe('KQL Custom Transform Generator', () => {
|
|||
|
||||
expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLO({
|
||||
indicator: createKQLCustomIndicator(),
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo);
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
log_timestamp: {
|
||||
gte: 'now-300s/m', // 2m + 2m + 60s
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template';
|
||||
import { KQLCustomIndicator, SLODefinition } from '../../domain/models';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { getTimesliceTargetComparator } from './common';
|
||||
import { getTimesliceTargetComparator, getFilterRange } from './common';
|
||||
|
||||
export class KQLCustomTransformGenerator extends TransformGenerator {
|
||||
public getTransformParams(slo: SLODefinition): TransformPutTransformRequest {
|
||||
|
@ -47,13 +47,7 @@ export class KQLCustomTransformGenerator extends TransformGenerator {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
[indicator.params.timestampField]: {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
getFilterRange(slo, indicator.params.timestampField),
|
||||
getElasticsearchQueryOrThrow(indicator.params.filter),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { twoMinute } from '../fixtures/duration';
|
||||
import {
|
||||
createMetricCustomIndicator,
|
||||
createSLO,
|
||||
|
@ -215,4 +216,28 @@ describe('Metric Custom Transform Generator', () => {
|
|||
|
||||
expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLO({
|
||||
indicator: createMetricCustomIndicator(),
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo);
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
log_timestamp: {
|
||||
gte: 'now-300s/m', // 2m + 2m + 60s
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_tr
|
|||
import { MetricCustomIndicator, SLODefinition } from '../../domain/models';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { GetCustomMetricIndicatorAggregation } from '../aggregations';
|
||||
import { getTimesliceTargetComparator } from './common';
|
||||
import { getTimesliceTargetComparator, getFilterRange } from './common';
|
||||
|
||||
export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g;
|
||||
|
||||
|
@ -50,13 +50,7 @@ export class MetricCustomTransformGenerator extends TransformGenerator {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
[indicator.params.timestampField]: {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
getFilterRange(slo, indicator.params.timestampField),
|
||||
getElasticsearchQueryOrThrow(indicator.params.filter),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ import { SLODefinition } from '../../domain/models';
|
|||
import { createSLO, createSyntheticsAvailabilityIndicator } from '../fixtures/slo';
|
||||
import { SyntheticsAvailabilityTransformGenerator } from './synthetics_availability';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
|
||||
import { twoMinute } from '../fixtures/duration';
|
||||
|
||||
const generator = new SyntheticsAvailabilityTransformGenerator();
|
||||
|
||||
|
@ -404,4 +405,28 @@ describe('Synthetics Availability Transform Generator', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLO({
|
||||
indicator: createSyntheticsAvailabilityIndicator(),
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo, 'default');
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-300s/m', // 2m + 2m + 60s
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,8 @@ import {
|
|||
import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template';
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { SLODefinition } from '../../domain/models';
|
||||
import { getFilterRange } from './common';
|
||||
|
||||
export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator {
|
||||
public getTransformParams(slo: SLODefinition, spaceId: string): TransformPutTransformRequest {
|
||||
if (!syntheticsAvailabilityIndicatorSchema.is(slo.indicator)) {
|
||||
|
@ -108,13 +110,7 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator
|
|||
const queryFilter: estypes.QueryDslQueryContainer[] = [
|
||||
{ term: { 'summary.final_attempt': true } },
|
||||
{ term: { 'meta.space_id': spaceId } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
getFilterRange(slo, '@timestamp'),
|
||||
];
|
||||
const { monitorIds, tags, projects } = buildParamValues({
|
||||
monitorIds: indicator.params.monitorIds || [],
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { twoMinute } from '../fixtures/duration';
|
||||
import {
|
||||
createTimesliceMetricIndicator,
|
||||
createSLOWithTimeslicesBudgetingMethod,
|
||||
|
@ -164,4 +165,28 @@ describe('Timeslice Metric Transform Generator', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("overrides the range filter when 'preventInitialBackfill' is true", () => {
|
||||
const slo = createSLOWithTimeslicesBudgetingMethod({
|
||||
indicator: everythingIndicator,
|
||||
settings: {
|
||||
frequency: twoMinute(),
|
||||
syncDelay: twoMinute(),
|
||||
preventInitialBackfill: true,
|
||||
},
|
||||
});
|
||||
|
||||
const transform = generator.getTransformParams(slo);
|
||||
|
||||
// @ts-ignore
|
||||
const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f);
|
||||
|
||||
expect(rangeFilter).toEqual({
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-360s/m', // 2m + 2m + 2m slice window
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
timesliceMetricIndicatorSchema,
|
||||
timeslicesBudgetingMethodSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
|
||||
import { InvalidTransformError } from '../../errors';
|
||||
import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template';
|
||||
import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
|
||||
|
@ -23,6 +22,7 @@ import {
|
|||
} from '../../../common/constants';
|
||||
import { SLODefinition } from '../../domain/models';
|
||||
import { GetTimesliceMetricIndicatorAggregation } from '../aggregations';
|
||||
import { getFilterRange } from './common';
|
||||
|
||||
const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g;
|
||||
|
||||
|
@ -55,13 +55,7 @@ export class TimesliceMetricTransformGenerator extends TransformGenerator {
|
|||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
[indicator.params.timestampField]: {
|
||||
gte: `now-${slo.timeWindow.duration.format()}/d`,
|
||||
},
|
||||
},
|
||||
},
|
||||
getFilterRange(slo, indicator.params.timestampField),
|
||||
getElasticsearchQueryOrThrow(indicator.params.filter),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@ Object {
|
|||
"unit": "m",
|
||||
"value": 1,
|
||||
},
|
||||
"preventInitialBackfill": false,
|
||||
"syncDelay": Duration {
|
||||
"unit": "m",
|
||||
"value": 1,
|
||||
|
@ -81,6 +82,7 @@ Object {
|
|||
"unit": "m",
|
||||
"value": 1,
|
||||
},
|
||||
"preventInitialBackfill": false,
|
||||
"syncDelay": Duration {
|
||||
"unit": "m",
|
||||
"value": 1,
|
||||
|
|
|
@ -29,7 +29,12 @@ export function fromRemoteSummaryDocumentToSloDefinition(
|
|||
timesliceTarget: summaryDoc.slo.objective.timesliceTarget ?? undefined,
|
||||
timesliceWindow: summaryDoc.slo.objective.timesliceWindow ?? undefined,
|
||||
},
|
||||
settings: { syncDelay: '1m', frequency: '1m' },
|
||||
settings: {
|
||||
syncDelay: '1m',
|
||||
frequency: '1m',
|
||||
// added in 8.15.0
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
revision: summaryDoc.slo.revision,
|
||||
enabled: true,
|
||||
tags: summaryDoc.slo.tags,
|
||||
|
|
|
@ -192,7 +192,11 @@ describe('UpdateSLO', () => {
|
|||
const slo = createSLO();
|
||||
mockRepository.findById.mockResolvedValueOnce(slo);
|
||||
|
||||
const newSettings = { ...slo.settings, timestamp_field: 'newField' };
|
||||
const newSettings = {
|
||||
...slo.settings,
|
||||
frequency: fiveMinute(),
|
||||
preventInitialBackfill: true,
|
||||
};
|
||||
await updateSLO.execute(slo.id, { settings: newSettings });
|
||||
|
||||
expectDeletionOfOriginalSLOResources(slo);
|
||||
|
|
|
@ -39,6 +39,7 @@ export class UpdateSLO {
|
|||
const originalSlo = await this.repository.findById(sloId);
|
||||
let updatedSlo: SLODefinition = Object.assign({}, originalSlo, params, {
|
||||
groupBy: !!params.groupBy ? params.groupBy : originalSlo.groupBy,
|
||||
settings: mergePartialSettings(originalSlo.settings, params.settings),
|
||||
});
|
||||
|
||||
if (isEqual(originalSlo, updatedSlo)) {
|
||||
|
@ -183,3 +184,13 @@ export class UpdateSLO {
|
|||
return updateSLOResponseSchema.encode(slo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings are merged by overwriting the original settings with the optional new partial settings.
|
||||
*/
|
||||
function mergePartialSettings(
|
||||
originalSettings: SLODefinition['settings'],
|
||||
newPartialSettings: UpdateSLOParams['settings']
|
||||
) {
|
||||
return Object.assign({}, originalSettings, newPartialSettings);
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
settings: {
|
||||
frequency: '1m',
|
||||
syncDelay: '1m',
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
tags: ['test'],
|
||||
timeWindow: {
|
||||
|
|
|
@ -94,6 +94,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
settings: {
|
||||
frequency: '1m',
|
||||
syncDelay: '1m',
|
||||
preventInitialBackfill: false,
|
||||
},
|
||||
tags: ['test'],
|
||||
timeWindow: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue