mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
feat(slo): Update search with the new summary data (#162336)
This commit is contained in:
parent
b760b2cbed
commit
473b9a4a7c
36 changed files with 570 additions and 739 deletions
|
@ -7,15 +7,17 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
import {
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
budgetingMethodSchema,
|
||||
dateType,
|
||||
durationType,
|
||||
histogramIndicatorSchema,
|
||||
historicalSummarySchema,
|
||||
indicatorSchema,
|
||||
indicatorTypesArraySchema,
|
||||
indicatorTypesSchema,
|
||||
kqlCustomIndicatorSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
histogramIndicatorSchema,
|
||||
objectiveSchema,
|
||||
optionalSettingsSchema,
|
||||
previewDataSchema,
|
||||
|
@ -24,9 +26,6 @@ import {
|
|||
summarySchema,
|
||||
tagsSchema,
|
||||
timeWindowSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
durationType,
|
||||
timeWindowTypeSchema,
|
||||
} from '../schema';
|
||||
|
||||
|
@ -69,12 +68,16 @@ const getSLOParamsSchema = t.type({
|
|||
});
|
||||
|
||||
const sortDirectionSchema = t.union([t.literal('asc'), t.literal('desc')]);
|
||||
const sortBySchema = t.union([t.literal('creationTime'), t.literal('indicatorType')]);
|
||||
const sortBySchema = t.union([
|
||||
t.literal('error_budget_consumed'),
|
||||
t.literal('error_budget_remaining'),
|
||||
t.literal('sli_value'),
|
||||
t.literal('status'),
|
||||
]);
|
||||
|
||||
const findSLOParamsSchema = t.partial({
|
||||
query: t.partial({
|
||||
name: t.string,
|
||||
indicatorTypes: indicatorTypesArraySchema,
|
||||
kqlQuery: t.string,
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
sortBy: sortBySchema,
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('SLO Selector', () => {
|
|||
render(<SloSelector onSelected={onSelectedSpy} />);
|
||||
|
||||
expect(screen.getByTestId('sloSelector')).toBeTruthy();
|
||||
expect(useFetchSloListMock).toHaveBeenCalledWith({ name: '' });
|
||||
expect(useFetchSloListMock).toHaveBeenCalledWith({ kqlQuery: 'slo.name:*' });
|
||||
});
|
||||
|
||||
it('searches SLOs when typing', async () => {
|
||||
|
@ -42,6 +42,6 @@ describe('SLO Selector', () => {
|
|||
await wait(310); // debounce delay
|
||||
});
|
||||
|
||||
expect(useFetchSloListMock).toHaveBeenCalledWith({ name: 'latency' });
|
||||
expect(useFetchSloListMock).toHaveBeenCalledWith({ kqlQuery: 'slo.name:latency*' });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ function SloSelector({ initialSlo, onSelected, errors }: Props) {
|
|||
const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<string>>>([]);
|
||||
const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>();
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const { isLoading, sloList } = useFetchSloList({ name: searchValue });
|
||||
const { isLoading, sloList } = useFetchSloList({ kqlQuery: `slo.name:${searchValue}*` });
|
||||
const hasError = errors !== undefined && errors.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
import type { Indicator } from '@kbn/slo-schema';
|
||||
|
||||
interface SloListFilter {
|
||||
name: string;
|
||||
kqlQuery: string;
|
||||
page: number;
|
||||
sortBy: string;
|
||||
indicatorTypes: string[];
|
||||
sortBy?: string;
|
||||
sortDirection: string;
|
||||
}
|
||||
|
||||
interface CompositeSloKeyFilter {
|
||||
|
|
|
@ -20,10 +20,10 @@ import { useKibana } from '../../utils/kibana_react';
|
|||
import { sloKeys } from './query_key_factory';
|
||||
|
||||
interface SLOListParams {
|
||||
name?: string;
|
||||
kqlQuery?: string;
|
||||
page?: number;
|
||||
sortBy?: string;
|
||||
indicatorTypes?: string[];
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
shouldRefetch?: boolean;
|
||||
}
|
||||
|
||||
|
@ -43,10 +43,10 @@ const SHORT_REFETCH_INTERVAL = 1000 * 5; // 5 seconds
|
|||
const LONG_REFETCH_INTERVAL = 1000 * 60; // 1 minute
|
||||
|
||||
export function useFetchSloList({
|
||||
name = '',
|
||||
kqlQuery = '',
|
||||
page = 1,
|
||||
sortBy = 'creationTime',
|
||||
indicatorTypes = [],
|
||||
sortBy,
|
||||
sortDirection = 'desc',
|
||||
shouldRefetch,
|
||||
}: SLOListParams | undefined = {}): UseFetchSloListResponse {
|
||||
const {
|
||||
|
@ -61,18 +61,15 @@ export function useFetchSloList({
|
|||
|
||||
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery(
|
||||
{
|
||||
queryKey: sloKeys.list({ name, page, sortBy, indicatorTypes }),
|
||||
queryKey: sloKeys.list({ kqlQuery, page, sortBy, sortDirection }),
|
||||
queryFn: async ({ signal }) => {
|
||||
try {
|
||||
const response = await http.get<FindSLOResponse>(`/api/observability/slos`, {
|
||||
query: {
|
||||
...(page && { page }),
|
||||
...(name && { name }),
|
||||
...(kqlQuery && { kqlQuery }),
|
||||
...(sortBy && { sortBy }),
|
||||
...(indicatorTypes &&
|
||||
indicatorTypes.length > 0 && {
|
||||
indicatorTypes: indicatorTypes.join(','),
|
||||
}),
|
||||
...(sortDirection && { sortDirection }),
|
||||
...(page && { page }),
|
||||
},
|
||||
signal,
|
||||
});
|
||||
|
|
|
@ -11,11 +11,7 @@ import { debounce } from 'lodash';
|
|||
import { useIsMutating } from '@tanstack/react-query';
|
||||
|
||||
import { useFetchSloList } from '../../../hooks/slo/use_fetch_slo_list';
|
||||
import {
|
||||
FilterType,
|
||||
SloListSearchFilterSortBar,
|
||||
SortType,
|
||||
} from './slo_list_search_filter_sort_bar';
|
||||
import { SloListSearchFilterSortBar, SortField } from './slo_list_search_filter_sort_bar';
|
||||
import { SloListItems } from './slo_list_items';
|
||||
|
||||
export interface Props {
|
||||
|
@ -26,14 +22,13 @@ export function SloList({ autoRefresh }: Props) {
|
|||
const [activePage, setActivePage] = useState(0);
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [sort, setSort] = useState<SortType>('creationTime');
|
||||
const [indicatorTypeFilter, setIndicatorTypeFilter] = useState<FilterType[]>([]);
|
||||
const [sort, setSort] = useState<SortField | undefined>('error_budget_remaining');
|
||||
|
||||
const { isInitialLoading, isLoading, isRefetching, isError, sloList, refetch } = useFetchSloList({
|
||||
page: activePage + 1,
|
||||
name: query,
|
||||
kqlQuery: query,
|
||||
sortBy: sort,
|
||||
indicatorTypes: indicatorTypeFilter,
|
||||
sortDirection: 'desc',
|
||||
shouldRefetch: autoRefresh,
|
||||
});
|
||||
|
||||
|
@ -53,18 +48,14 @@ export function SloList({ autoRefresh }: Props) {
|
|||
() =>
|
||||
debounce((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(e.target.value);
|
||||
}, 300),
|
||||
}, 800),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChangeSort = (newSort: SortType) => {
|
||||
const handleChangeSort = (newSort: SortField | undefined) => {
|
||||
setSort(newSort);
|
||||
};
|
||||
|
||||
const handleChangeIndicatorTypeFilter = (newFilter: FilterType[]) => {
|
||||
setIndicatorTypeFilter(newFilter);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="m" data-test-subj="sloList">
|
||||
<EuiFlexItem grow>
|
||||
|
@ -80,7 +71,6 @@ export function SloList({ autoRefresh }: Props) {
|
|||
}
|
||||
onChangeQuery={handleChangeQuery}
|
||||
onChangeSort={handleChangeSort}
|
||||
onChangeIndicatorTypeFilter={handleChangeIndicatorTypeFilter}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ const defaultProps: SloListSearchFilterSortBarProps = {
|
|||
loading: false,
|
||||
onChangeQuery: () => {},
|
||||
onChangeSort: () => {},
|
||||
onChangeIndicatorTypeFilter: () => {},
|
||||
};
|
||||
|
||||
export const SloListSearchFilterSortBar = Template.bind({});
|
||||
|
|
|
@ -18,29 +18,15 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
INDICATOR_APM_AVAILABILITY,
|
||||
INDICATOR_APM_LATENCY,
|
||||
INDICATOR_CUSTOM_KQL,
|
||||
INDICATOR_CUSTOM_METRIC,
|
||||
INDICATOR_HISTOGRAM,
|
||||
} from '../../../utils/slo/labels';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export interface SloListSearchFilterSortBarProps {
|
||||
loading: boolean;
|
||||
onChangeQuery: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onChangeSort: (sort: SortType) => void;
|
||||
onChangeIndicatorTypeFilter: (filter: FilterType[]) => void;
|
||||
onChangeSort: (sort: SortField | undefined) => void;
|
||||
}
|
||||
|
||||
export type SortType = 'creationTime' | 'indicatorType';
|
||||
export type FilterType =
|
||||
| 'sli.apm.transactionDuration'
|
||||
| 'sli.apm.transactionErrorRate'
|
||||
| 'sli.kql.custom'
|
||||
| 'sli.metric.custom'
|
||||
| 'sli.histogram.custom';
|
||||
export type SortField = 'sli_value' | 'error_budget_consumed' | 'error_budget_remaining' | 'status';
|
||||
|
||||
export type Item<T> = EuiSelectableOption & {
|
||||
label: string;
|
||||
|
@ -48,83 +34,50 @@ export type Item<T> = EuiSelectableOption & {
|
|||
checked?: EuiSelectableOptionCheckedType;
|
||||
};
|
||||
|
||||
const SORT_OPTIONS: Array<Item<SortType>> = [
|
||||
const SORT_OPTIONS: Array<Item<SortField>> = [
|
||||
{
|
||||
label: i18n.translate('xpack.observability.slo.list.sortBy.creationTime', {
|
||||
defaultMessage: 'Creation time',
|
||||
label: i18n.translate('xpack.observability.slo.list.sortBy.sliValue', {
|
||||
defaultMessage: 'SLI value',
|
||||
}),
|
||||
type: 'creationTime',
|
||||
type: 'sli_value',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.observability.slo.list.sortBy.sloStatus', {
|
||||
defaultMessage: 'SLO status',
|
||||
}),
|
||||
type: 'status',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetConsumed', {
|
||||
defaultMessage: 'Error budget consumed',
|
||||
}),
|
||||
type: 'error_budget_consumed',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetRemaining', {
|
||||
defaultMessage: 'Error budget remaining',
|
||||
}),
|
||||
type: 'error_budget_remaining',
|
||||
checked: 'on',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.observability.slo.list.sortBy.indicatorType', {
|
||||
defaultMessage: 'Indicator type',
|
||||
}),
|
||||
type: 'indicatorType',
|
||||
},
|
||||
];
|
||||
|
||||
const INDICATOR_TYPE_OPTIONS: Array<Item<FilterType>> = [
|
||||
{
|
||||
label: INDICATOR_APM_LATENCY,
|
||||
type: 'sli.apm.transactionDuration',
|
||||
},
|
||||
{
|
||||
label: INDICATOR_APM_AVAILABILITY,
|
||||
type: 'sli.apm.transactionErrorRate',
|
||||
},
|
||||
{
|
||||
label: INDICATOR_CUSTOM_KQL,
|
||||
type: 'sli.kql.custom',
|
||||
},
|
||||
{
|
||||
label: INDICATOR_CUSTOM_METRIC,
|
||||
type: 'sli.metric.custom',
|
||||
},
|
||||
{
|
||||
label: INDICATOR_HISTOGRAM,
|
||||
type: 'sli.histogram.custom',
|
||||
},
|
||||
];
|
||||
|
||||
export function SloListSearchFilterSortBar({
|
||||
loading,
|
||||
onChangeQuery,
|
||||
onChangeSort,
|
||||
onChangeIndicatorTypeFilter,
|
||||
}: SloListSearchFilterSortBarProps) {
|
||||
const [isFilterPopoverOpen, setFilterPopoverOpen] = useState(false);
|
||||
const [isSortPopoverOpen, setSortPopoverOpen] = useState(false);
|
||||
|
||||
const [sortOptions, setSortOptions] = useState(SORT_OPTIONS);
|
||||
const [indicatorTypeOptions, setIndicatorTypeOptions] = useState(INDICATOR_TYPE_OPTIONS);
|
||||
|
||||
const selectedSort = sortOptions.find((option) => option.checked === 'on');
|
||||
const selectedIndicatorTypeFilter = indicatorTypeOptions.filter(
|
||||
(option) => option.checked === 'on'
|
||||
);
|
||||
|
||||
const handleToggleFilterButton = () => setFilterPopoverOpen(!isFilterPopoverOpen);
|
||||
const handleToggleSortButton = () => setSortPopoverOpen(!isSortPopoverOpen);
|
||||
|
||||
const handleChangeSort = (newOptions: Array<Item<SortType>>) => {
|
||||
const handleChangeSort = (newOptions: Array<Item<SortField>>) => {
|
||||
setSortOptions(newOptions);
|
||||
setSortPopoverOpen(false);
|
||||
onChangeSort(newOptions.find((o) => o.checked)?.type);
|
||||
};
|
||||
|
||||
const handleChangeIndicatorTypeOptions = (newOptions: Array<Item<FilterType>>) => {
|
||||
setIndicatorTypeOptions(newOptions);
|
||||
onChangeIndicatorTypeFilter(
|
||||
newOptions.filter((option) => option.checked === 'on').map((option) => option.type)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSort?.type === 'creationTime' || selectedSort?.type === 'indicatorType') {
|
||||
onChangeSort(selectedSort.type);
|
||||
}
|
||||
}, [onChangeSort, selectedSort]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
<EuiFlexItem grow>
|
||||
|
@ -139,44 +92,7 @@ export function SloListSearchFilterSortBar({
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false} style={{ width: 200 }}>
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiFilterButton
|
||||
iconType="arrowDown"
|
||||
onClick={handleToggleFilterButton}
|
||||
isSelected={isFilterPopoverOpen}
|
||||
numFilters={selectedIndicatorTypeFilter.length}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slo.list.indicatorTypeFilter', {
|
||||
defaultMessage: 'Indicator type',
|
||||
})}
|
||||
</EuiFilterButton>
|
||||
}
|
||||
isOpen={isFilterPopoverOpen}
|
||||
closePopover={handleToggleFilterButton}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downCenter"
|
||||
>
|
||||
<div style={{ width: 300 }}>
|
||||
<EuiPopoverTitle paddingSize="s">
|
||||
{i18n.translate('xpack.observability.slo.list.indicatorTypeFilter', {
|
||||
defaultMessage: 'Indicator type',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiSelectable<Item<FilterType>>
|
||||
options={indicatorTypeOptions}
|
||||
onChange={handleChangeIndicatorTypeOptions}
|
||||
>
|
||||
{(list) => list}
|
||||
</EuiSelectable>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false} style={{ width: 200 }}>
|
||||
<EuiFlexItem grow={true} style={{ maxWidth: 280 }}>
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
button={
|
||||
|
@ -202,7 +118,7 @@ export function SloListSearchFilterSortBar({
|
|||
defaultMessage: 'Sort by',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiSelectable<Item<SortType>>
|
||||
<EuiSelectable<Item<SortField>>
|
||||
singleSelection
|
||||
options={sortOptions}
|
||||
onChange={handleChangeSort}
|
||||
|
|
|
@ -103,9 +103,16 @@ export const getSLOSummaryMappingsTemplate = (name: string) => ({
|
|||
errorBudgetRemaining: {
|
||||
type: 'double',
|
||||
},
|
||||
status: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
},
|
||||
statusCode: {
|
||||
type: 'byte',
|
||||
},
|
||||
status: {
|
||||
type: 'keyword',
|
||||
ignore_above: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -29,5 +29,7 @@ export const SLO_SUMMARY_TRANSFORM_NAME_PREFIX = 'slo-summary-';
|
|||
export const SLO_SUMMARY_DESTINATION_INDEX_NAME = `${SLO_SUMMARY_INDEX_TEMPLATE_NAME}-v${SLO_RESOURCES_VERSION}`;
|
||||
export const SLO_SUMMARY_DESTINATION_INDEX_PATTERN = `${SLO_SUMMARY_DESTINATION_INDEX_NAME}*`;
|
||||
|
||||
export const SLO_SUMMARY_INGEST_PIPELINE_NAME = `${SLO_SUMMARY_INDEX_TEMPLATE_NAME}.pipeline`;
|
||||
|
||||
export const getSLOTransformId = (sloId: string, sloRevision: number) =>
|
||||
`slo-${sloId}-${sloRevision}`;
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SLO_RESOURCES_VERSION } from '../constants';
|
||||
|
||||
export const getSLOSummaryPipelineTemplate = (id: string) => ({
|
||||
id,
|
||||
description: 'SLO summary ingest pipeline',
|
||||
processors: [
|
||||
{
|
||||
split: {
|
||||
description: 'Split comma separated list of tags into an array',
|
||||
field: 'slo.tags',
|
||||
separator: ',',
|
||||
},
|
||||
},
|
||||
{
|
||||
set: {
|
||||
description: "if 'statusCode == 0', set status to NO_DATA",
|
||||
if: 'ctx.statusCode == 0',
|
||||
field: 'status',
|
||||
value: 'NO_DATA',
|
||||
},
|
||||
},
|
||||
{
|
||||
set: {
|
||||
description: "if 'statusCode == 1', set statusLabel to VIOLATED",
|
||||
if: 'ctx.statusCode == 1',
|
||||
field: 'status',
|
||||
value: 'VIOLATED',
|
||||
},
|
||||
},
|
||||
{
|
||||
set: {
|
||||
description: "if 'statusCode == 2', set status to DEGRADING",
|
||||
if: 'ctx.statusCode == 2',
|
||||
field: 'status',
|
||||
value: 'DEGRADING',
|
||||
},
|
||||
},
|
||||
{
|
||||
set: {
|
||||
description: "if 'statusCode == 4', set status to HEALTHY",
|
||||
if: 'ctx.statusCode == 4',
|
||||
field: 'status',
|
||||
value: 'HEALTHY',
|
||||
},
|
||||
},
|
||||
],
|
||||
_meta: {
|
||||
description: 'SLO summary ingest pipeline',
|
||||
version: SLO_RESOURCES_VERSION,
|
||||
managed: true,
|
||||
managed_by: 'observability',
|
||||
},
|
||||
});
|
|
@ -36,6 +36,7 @@ import { getGlobalDiagnosis, getSloDiagnosis } from '../../services/slo/get_diag
|
|||
import { GetPreviewData } from '../../services/slo/get_preview_data';
|
||||
import { DefaultHistoricalSummaryClient } from '../../services/slo/historical_summary_client';
|
||||
import { ManageSLO } from '../../services/slo/manage_slo';
|
||||
import { DefaultSummarySearchClient } from '../../services/slo/summary_search_client';
|
||||
import { DefaultSummaryTransformInstaller } from '../../services/slo/summary_transform/summary_transform_installer';
|
||||
import {
|
||||
ApmTransactionDurationTransformGenerator,
|
||||
|
@ -239,7 +240,7 @@ const findSLORoute = createObservabilityServerRoute({
|
|||
tags: ['access:slo_read'],
|
||||
},
|
||||
params: findSLOParamsSchema,
|
||||
handler: async ({ context, params }) => {
|
||||
handler: async ({ context, params, logger }) => {
|
||||
const hasCorrectLicense = await isLicenseAtLeastPlatinum(context);
|
||||
|
||||
if (!hasCorrectLicense) {
|
||||
|
@ -249,8 +250,8 @@ const findSLORoute = createObservabilityServerRoute({
|
|||
const soClient = (await context.core).savedObjects.client;
|
||||
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClient);
|
||||
const summaryClient = new DefaultSummaryClient(esClient);
|
||||
const findSLO = new FindSLO(repository, summaryClient);
|
||||
const summarySearchClient = new DefaultSummarySearchClient(esClient, logger);
|
||||
const findSLO = new FindSLO(repository, summarySearchClient);
|
||||
|
||||
const response = await findSLO.execute(params?.query ?? {});
|
||||
|
||||
|
|
|
@ -5,37 +5,45 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SLO, SLOId, Summary } from '../../domain/models';
|
||||
import { SLO } from '../../domain/models';
|
||||
import { FindSLO } from './find_slo';
|
||||
import { createSLO, createPaginatedSLO } from './fixtures/slo';
|
||||
import { createSummaryClientMock, createSLORepositoryMock } from './mocks';
|
||||
import { SLORepository, SortField, SortDirection } from './slo_repository';
|
||||
import { SummaryClient } from './summary_client';
|
||||
import { createSLO } from './fixtures/slo';
|
||||
import { createSLORepositoryMock, createSummarySearchClientMock } from './mocks';
|
||||
import { SLORepository } from './slo_repository';
|
||||
import { Paginated, SLOSummary, SummarySearchClient } from './summary_search_client';
|
||||
|
||||
describe('FindSLO', () => {
|
||||
let mockRepository: jest.Mocked<SLORepository>;
|
||||
let mockSummaryClient: jest.Mocked<SummaryClient>;
|
||||
let mockSummarySearchClient: jest.Mocked<SummarySearchClient>;
|
||||
let findSLO: FindSLO;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRepository = createSLORepositoryMock();
|
||||
mockSummaryClient = createSummaryClientMock();
|
||||
findSLO = new FindSLO(mockRepository, mockSummaryClient);
|
||||
mockSummarySearchClient = createSummarySearchClientMock();
|
||||
findSLO = new FindSLO(mockRepository, mockSummarySearchClient);
|
||||
});
|
||||
|
||||
describe('happy path', () => {
|
||||
it('returns the results with pagination', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
mockSummarySearchClient.search.mockResolvedValueOnce(summarySearchResult(slo));
|
||||
mockRepository.findAllByIds.mockResolvedValueOnce([slo]);
|
||||
|
||||
const result = await findSLO.execute({});
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: undefined },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
expect(mockSummarySearchClient.search.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"",
|
||||
Object {
|
||||
"direction": "asc",
|
||||
"field": "status",
|
||||
},
|
||||
Object {
|
||||
"page": 1,
|
||||
"perPage": 25,
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
|
@ -89,131 +97,65 @@ describe('FindSLO', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls the repository with the default criteria and pagination', async () => {
|
||||
it('calls the repository with all the summary slo ids', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
mockSummarySearchClient.search.mockResolvedValueOnce(summarySearchResult(slo));
|
||||
mockRepository.findAllByIds.mockResolvedValueOnce([slo]);
|
||||
|
||||
await findSLO.execute({});
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: undefined },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
expect(mockRepository.findAllByIds).toHaveBeenCalledWith([slo.id]);
|
||||
});
|
||||
|
||||
it('calls the repository with the name filter criteria', async () => {
|
||||
it('searches with the provided criteria', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
mockSummarySearchClient.search.mockResolvedValueOnce(summarySearchResult(slo));
|
||||
mockRepository.findAllByIds.mockResolvedValueOnce([slo]);
|
||||
|
||||
await findSLO.execute({ name: 'Availability' });
|
||||
await findSLO.execute({
|
||||
kqlQuery: "slo.name:'Service*' and slo.indicator.type:'sli.kql.custom'",
|
||||
page: '2',
|
||||
perPage: '10',
|
||||
sortBy: 'error_budget_consumed',
|
||||
sortDirection: 'asc',
|
||||
});
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: 'Availability' },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the repository with the indicatorType filter criteria', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
|
||||
await findSLO.execute({ indicatorTypes: ['sli.kql.custom'] });
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ indicatorTypes: ['sli.kql.custom'] },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the repository with the pagination', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
|
||||
await findSLO.execute({ name: 'My SLO*', page: '2', perPage: '100' });
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: 'My SLO*' },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 2, perPage: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
it('uses default pagination values when invalid', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
|
||||
await findSLO.execute({ page: '-1', perPage: '0' });
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: undefined },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
});
|
||||
|
||||
it('sorts by name by default when not specified', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
|
||||
await findSLO.execute({ sortBy: undefined });
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: undefined },
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
});
|
||||
|
||||
it('sorts by indicator type', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
|
||||
await findSLO.execute({ sortBy: 'indicatorType' });
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: undefined },
|
||||
{ field: SortField.IndicatorType, direction: SortDirection.Asc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
});
|
||||
|
||||
it('sorts by indicator type in descending order', async () => {
|
||||
const slo = createSLO();
|
||||
mockRepository.find.mockResolvedValueOnce(createPaginatedSLO(slo));
|
||||
mockSummaryClient.fetchSummary.mockResolvedValueOnce(someSummary(slo));
|
||||
|
||||
await findSLO.execute({ sortBy: 'indicatorType', sortDirection: 'desc' });
|
||||
|
||||
expect(mockRepository.find).toHaveBeenCalledWith(
|
||||
{ name: undefined },
|
||||
{ field: SortField.IndicatorType, direction: SortDirection.Desc },
|
||||
{ page: 1, perPage: 25 }
|
||||
);
|
||||
expect(mockSummarySearchClient.search.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"slo.name:'Service*' and slo.indicator.type:'sli.kql.custom'",
|
||||
Object {
|
||||
"direction": "asc",
|
||||
"field": "error_budget_consumed",
|
||||
},
|
||||
Object {
|
||||
"page": 2,
|
||||
"perPage": 10,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function someSummary(slo: SLO): Record<SLOId, Summary> {
|
||||
function summarySearchResult(slo: SLO): Paginated<SLOSummary> {
|
||||
return {
|
||||
[slo.id]: {
|
||||
status: 'HEALTHY',
|
||||
sliValue: 0.9999,
|
||||
errorBudget: {
|
||||
initial: 0.001,
|
||||
consumed: 0.1,
|
||||
remaining: 0.9,
|
||||
isEstimated: false,
|
||||
total: 1,
|
||||
perPage: 25,
|
||||
page: 1,
|
||||
results: [
|
||||
{
|
||||
id: slo.id,
|
||||
summary: {
|
||||
status: 'HEALTHY',
|
||||
sliValue: 0.9999,
|
||||
errorBudget: {
|
||||
initial: 0.001,
|
||||
consumed: 0.1,
|
||||
remaining: 0.9,
|
||||
isEstimated: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,54 +6,44 @@
|
|||
*/
|
||||
|
||||
import { FindSLOParams, FindSLOResponse, findSLOResponseSchema } from '@kbn/slo-schema';
|
||||
import { SLO, SLOId, SLOWithSummary, Summary } from '../../domain/models';
|
||||
import {
|
||||
Criteria,
|
||||
Paginated,
|
||||
Pagination,
|
||||
SLORepository,
|
||||
Sort,
|
||||
SortField,
|
||||
SortDirection,
|
||||
} from './slo_repository';
|
||||
import { SummaryClient } from './summary_client';
|
||||
import { SLO, SLOWithSummary } from '../../domain/models';
|
||||
import { SLORepository } from './slo_repository';
|
||||
import { Pagination, SLOSummary, Sort, SummarySearchClient } from './summary_search_client';
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const DEFAULT_PER_PAGE = 25;
|
||||
|
||||
export class FindSLO {
|
||||
constructor(private repository: SLORepository, private summaryClient: SummaryClient) {}
|
||||
constructor(
|
||||
private repository: SLORepository,
|
||||
private summarySearchClient: SummarySearchClient
|
||||
) {}
|
||||
|
||||
public async execute(params: FindSLOParams): Promise<FindSLOResponse> {
|
||||
const pagination: Pagination = toPagination(params);
|
||||
const criteria: Criteria = toCriteria(params);
|
||||
const sort: Sort = toSort(params);
|
||||
|
||||
const { results: sloList, ...resultMeta }: Paginated<SLO> = await this.repository.find(
|
||||
criteria,
|
||||
sort,
|
||||
const sloSummaryList = await this.summarySearchClient.search(
|
||||
params.kqlQuery ?? '',
|
||||
toSort(params),
|
||||
pagination
|
||||
);
|
||||
const summaryBySlo = await this.summaryClient.fetchSummary(sloList);
|
||||
|
||||
const sloListWithSummary = mergeSloWithSummary(sloList, summaryBySlo);
|
||||
const sloList = await this.repository.findAllByIds(sloSummaryList.results.map((slo) => slo.id));
|
||||
const sloListWithSummary = mergeSloWithSummary(sloList, sloSummaryList.results);
|
||||
|
||||
return findSLOResponseSchema.encode({
|
||||
page: resultMeta.page,
|
||||
perPage: resultMeta.perPage,
|
||||
total: resultMeta.total,
|
||||
page: sloSummaryList.page,
|
||||
perPage: sloSummaryList.perPage,
|
||||
total: sloSummaryList.total,
|
||||
results: sloListWithSummary,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mergeSloWithSummary(
|
||||
sloList: SLO[],
|
||||
summaryBySlo: Record<SLOId, Summary>
|
||||
): SLOWithSummary[] {
|
||||
return sloList.map((slo) => ({
|
||||
...slo,
|
||||
summary: summaryBySlo[slo.id],
|
||||
function mergeSloWithSummary(sloList: SLO[], sloSummaryList: SLOSummary[]): SLOWithSummary[] {
|
||||
return sloSummaryList.map((sloSummary) => ({
|
||||
...sloList.find((s) => s.id === sloSummary.id)!,
|
||||
summary: sloSummary.summary,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -67,13 +57,9 @@ function toPagination(params: FindSLOParams): Pagination {
|
|||
};
|
||||
}
|
||||
|
||||
function toCriteria(params: FindSLOParams): Criteria {
|
||||
return { name: params.name, indicatorTypes: params.indicatorTypes };
|
||||
}
|
||||
|
||||
function toSort(params: FindSLOParams): Sort {
|
||||
return {
|
||||
field: params.sortBy === 'indicatorType' ? SortField.IndicatorType : SortField.CreationTime,
|
||||
direction: params.sortDirection === 'desc' ? SortDirection.Desc : SortDirection.Asc,
|
||||
field: params.sortBy ?? 'status',
|
||||
direction: params.sortDirection ?? 'asc',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
StoredSLO,
|
||||
} from '../../../domain/models';
|
||||
import { SO_SLO_TYPE } from '../../../saved_objects';
|
||||
import { Paginated } from '../slo_repository';
|
||||
import { twoMinute } from './duration';
|
||||
import { sevenDaysRolling, weeklyCalendarAligned } from './time_window';
|
||||
|
||||
|
@ -187,16 +186,3 @@ export const createSLOWithCalendarTimeWindow = (params: Partial<SLO> = {}): SLO
|
|||
...params,
|
||||
});
|
||||
};
|
||||
|
||||
export const createPaginatedSLO = (
|
||||
slo: SLO,
|
||||
params: Partial<Paginated<SLO>> = {}
|
||||
): Paginated<SLO> => {
|
||||
return {
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [slo],
|
||||
...params,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ResourceInstaller } from '../resource_installer';
|
|||
import { SLIClient } from '../sli_client';
|
||||
import { SLORepository } from '../slo_repository';
|
||||
import { SummaryClient } from '../summary_client';
|
||||
import { SummarySearchClient } from '../summary_search_client';
|
||||
import { TransformManager } from '../transform_manager';
|
||||
|
||||
const createResourceInstallerMock = (): jest.Mocked<ResourceInstaller> => {
|
||||
|
@ -32,7 +33,6 @@ const createSLORepositoryMock = (): jest.Mocked<SLORepository> => {
|
|||
findById: jest.fn(),
|
||||
findAllByIds: jest.fn(),
|
||||
deleteById: jest.fn(),
|
||||
find: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -42,6 +42,12 @@ const createSummaryClientMock = (): jest.Mocked<SummaryClient> => {
|
|||
};
|
||||
};
|
||||
|
||||
const createSummarySearchClientMock = (): jest.Mocked<SummarySearchClient> => {
|
||||
return {
|
||||
search: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const createSLIClientMock = (): jest.Mocked<SLIClient> => {
|
||||
return {
|
||||
fetchSLIDataFrom: jest.fn(),
|
||||
|
@ -53,5 +59,6 @@ export {
|
|||
createTransformManagerMock,
|
||||
createSLORepositoryMock,
|
||||
createSummaryClientMock,
|
||||
createSummarySearchClientMock,
|
||||
createSLIClientMock,
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME,
|
||||
SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME,
|
||||
SLO_SUMMARY_INDEX_TEMPLATE_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
} from '../../assets/constants';
|
||||
import { DefaultResourceInstaller } from './resource_installer';
|
||||
|
||||
|
@ -54,9 +55,16 @@ describe('resourceInstaller', () => {
|
|||
2,
|
||||
expect.objectContaining({ name: SLO_SUMMARY_INDEX_TEMPLATE_NAME })
|
||||
);
|
||||
expect(mockClusterClient.ingest.putPipeline).toHaveBeenCalledWith(
|
||||
|
||||
expect(mockClusterClient.ingest.putPipeline).toHaveBeenCalledTimes(2);
|
||||
expect(mockClusterClient.ingest.putPipeline).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ id: SLO_INGEST_PIPELINE_NAME })
|
||||
);
|
||||
expect(mockClusterClient.ingest.putPipeline).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ id: SLO_SUMMARY_INGEST_PIPELINE_NAME })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -91,6 +99,10 @@ describe('resourceInstaller', () => {
|
|||
// @ts-ignore _meta not typed properly
|
||||
[SLO_INGEST_PIPELINE_NAME]: { _meta: { version: SLO_RESOURCES_VERSION } },
|
||||
});
|
||||
mockClusterClient.ingest.getPipeline.mockResponseOnce({
|
||||
// @ts-ignore _meta not typed properly
|
||||
[SLO_SUMMARY_INGEST_PIPELINE_NAME]: { _meta: { version: SLO_RESOURCES_VERSION } },
|
||||
});
|
||||
const installer = new DefaultResourceInstaller(mockClusterClient, loggerMock.create());
|
||||
|
||||
await installer.ensureCommonResourcesInstalled();
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
SLO_SUMMARY_INDEX_TEMPLATE_PATTERN,
|
||||
SLO_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
} from '../../assets/constants';
|
||||
import { getSLOMappingsTemplate } from '../../assets/component_templates/slo_mappings_template';
|
||||
import { getSLOSettingsTemplate } from '../../assets/component_templates/slo_settings_template';
|
||||
|
@ -35,6 +36,7 @@ import { getSLOSummaryMappingsTemplate } from '../../assets/component_templates/
|
|||
import { getSLOSummarySettingsTemplate } from '../../assets/component_templates/slo_summary_settings_template';
|
||||
import { getSLOSummaryIndexTemplate } from '../../assets/index_templates/slo_summary_index_templates';
|
||||
import { retryTransientEsErrors } from '../../utils/retry';
|
||||
import { getSLOSummaryPipelineTemplate } from '../../assets/ingest_templates/slo_summary_pipeline_template';
|
||||
|
||||
export interface ResourceInstaller {
|
||||
ensureCommonResourcesInstalled(): Promise<void>;
|
||||
|
@ -94,6 +96,10 @@ export class DefaultResourceInstaller implements ResourceInstaller {
|
|||
await this.createOrUpdateIngestPipelineTemplate(
|
||||
getSLOPipelineTemplate(SLO_INGEST_PIPELINE_NAME, SLO_INGEST_PIPELINE_INDEX_NAME_PREFIX)
|
||||
);
|
||||
|
||||
await this.createOrUpdateIngestPipelineTemplate(
|
||||
getSLOSummaryPipelineTemplate(SLO_SUMMARY_INGEST_PIPELINE_NAME)
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.error(`Error installing resources shared for SLO: ${err.message}`);
|
||||
throw err;
|
||||
|
@ -149,7 +155,26 @@ export class DefaultResourceInstaller implements ResourceInstaller {
|
|||
return false;
|
||||
}
|
||||
|
||||
return indexTemplateExists && summaryIndexTemplateExists && ingestPipelineExists;
|
||||
let summaryIngestPipelineExists = false;
|
||||
try {
|
||||
const pipeline = await this.execute(() =>
|
||||
this.esClient.ingest.getPipeline({ id: SLO_SUMMARY_INGEST_PIPELINE_NAME })
|
||||
);
|
||||
|
||||
summaryIngestPipelineExists =
|
||||
pipeline &&
|
||||
// @ts-ignore _meta is not defined on the type
|
||||
pipeline[SLO_SUMMARY_INGEST_PIPELINE_NAME]._meta.version === SLO_RESOURCES_VERSION;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
indexTemplateExists &&
|
||||
summaryIndexTemplateExists &&
|
||||
ingestPipelineExists &&
|
||||
summaryIngestPipelineExists
|
||||
);
|
||||
}
|
||||
|
||||
private async createOrUpdateComponentTemplate(template: ClusterPutComponentTemplateRequest) {
|
||||
|
|
|
@ -8,23 +8,16 @@
|
|||
import { SavedObjectsClientContract, SavedObjectsFindResponse } from '@kbn/core/server';
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import { sloSchema } from '@kbn/slo-schema';
|
||||
|
||||
import { SLO, StoredSLO } from '../../domain/models';
|
||||
import { SO_SLO_TYPE } from '../../saved_objects';
|
||||
import {
|
||||
KibanaSavedObjectsSLORepository,
|
||||
Pagination,
|
||||
Sort,
|
||||
SortDirection,
|
||||
SortField,
|
||||
} from './slo_repository';
|
||||
import { createAPMTransactionDurationIndicator, createSLO, aStoredSLO } from './fixtures/slo';
|
||||
import { SLOIdConflict, SLONotFound } from '../../errors';
|
||||
import { SO_SLO_TYPE } from '../../saved_objects';
|
||||
import { aStoredSLO, createAPMTransactionDurationIndicator, createSLO } from './fixtures/slo';
|
||||
import { KibanaSavedObjectsSLORepository } from './slo_repository';
|
||||
|
||||
const SOME_SLO = createSLO({ indicator: createAPMTransactionDurationIndicator() });
|
||||
const ANOTHER_SLO = createSLO();
|
||||
|
||||
function createFindResponse(sloList: SLO[]): SavedObjectsFindResponse<StoredSLO> {
|
||||
function soFindResponse(sloList: SLO[]): SavedObjectsFindResponse<StoredSLO> {
|
||||
return {
|
||||
page: 1,
|
||||
per_page: 25,
|
||||
|
@ -48,7 +41,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
|
||||
describe('validation', () => {
|
||||
it('findById throws when an SLO is not found', async () => {
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([]));
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
|
||||
await expect(repository.findById('inexistant-slo-id')).rejects.toThrowError(
|
||||
|
@ -57,7 +50,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
});
|
||||
|
||||
it('deleteById throws when an SLO is not found', async () => {
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([]));
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
|
||||
await expect(repository.deleteById('inexistant-slo-id')).rejects.toThrowError(
|
||||
|
@ -69,7 +62,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
describe('saving an SLO', () => {
|
||||
it('saves the new SLO', async () => {
|
||||
const slo = createSLO({ id: 'my-id' });
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([]));
|
||||
soClientMock.create.mockResolvedValueOnce(aStoredSLO(slo));
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
|
||||
|
@ -90,7 +83,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
|
||||
it('throws when the SLO id already exists and "throwOnConflict" is true', async () => {
|
||||
const slo = createSLO({ id: 'my-id' });
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([slo]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([slo]));
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
|
||||
await expect(repository.save(slo, { throwOnConflict: true })).rejects.toThrowError(
|
||||
|
@ -106,7 +99,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
|
||||
it('updates the existing SLO', async () => {
|
||||
const slo = createSLO({ id: 'my-id' });
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([slo]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([slo]));
|
||||
soClientMock.create.mockResolvedValueOnce(aStoredSLO(slo));
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
|
||||
|
@ -128,7 +121,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
|
||||
it('finds an existing SLO', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([SOME_SLO]));
|
||||
|
||||
const foundSLO = await repository.findById(SOME_SLO.id);
|
||||
|
||||
|
@ -143,7 +136,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
|
||||
it('finds all SLOs by ids', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO, ANOTHER_SLO]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([SOME_SLO, ANOTHER_SLO]));
|
||||
|
||||
const results = await repository.findAllByIds([SOME_SLO.id, ANOTHER_SLO.id]);
|
||||
|
||||
|
@ -158,7 +151,7 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
|
||||
it('deletes an SLO', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
soClientMock.find.mockResolvedValueOnce(soFindResponse([SOME_SLO]));
|
||||
|
||||
await repository.deleteById(SOME_SLO.id);
|
||||
|
||||
|
@ -170,238 +163,4 @@ describe('KibanaSavedObjectsSLORepository', () => {
|
|||
});
|
||||
expect(soClientMock.delete).toHaveBeenCalledWith(SO_SLO_TYPE, SOME_SLO.id);
|
||||
});
|
||||
|
||||
describe('find', () => {
|
||||
const DEFAULT_PAGINATION: Pagination = { page: 1, perPage: 25 };
|
||||
const DEFAULT_SORTING: Sort = {
|
||||
field: SortField.CreationTime,
|
||||
direction: SortDirection.Asc,
|
||||
};
|
||||
|
||||
describe('Name search', () => {
|
||||
it('includes the search on name with wildcard when provided', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
const result = await repository.find(
|
||||
{ name: 'availability*' },
|
||||
DEFAULT_SORTING,
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [SOME_SLO],
|
||||
});
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
filter: undefined,
|
||||
search: '*availability*',
|
||||
searchFields: ['name'],
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes the search on name with added wildcard when not provided', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
const result = await repository.find(
|
||||
{ name: 'availa' },
|
||||
DEFAULT_SORTING,
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [SOME_SLO],
|
||||
});
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
filter: undefined,
|
||||
search: '*availa*',
|
||||
searchFields: ['name'],
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('indicatorTypes filter', () => {
|
||||
it('includes the filter on indicator types when provided', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
const result = await repository.find(
|
||||
{ indicatorTypes: ['sli.kql.custom'] },
|
||||
DEFAULT_SORTING,
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [SOME_SLO],
|
||||
});
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
filter: `(slo.attributes.indicator.type: sli.kql.custom)`,
|
||||
search: undefined,
|
||||
searchFields: undefined,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes the filter on indicator types as logical OR when provided', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
const result = await repository.find(
|
||||
{ indicatorTypes: ['sli.kql.custom', 'sli.apm.transactionDuration'] },
|
||||
DEFAULT_SORTING,
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [SOME_SLO],
|
||||
});
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
filter: `(slo.attributes.indicator.type: sli.kql.custom or slo.attributes.indicator.type: sli.apm.transactionDuration)`,
|
||||
search: undefined,
|
||||
searchFields: undefined,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('includes search on name and filter on indicator types', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
const result = await repository.find(
|
||||
{ name: 'latency', indicatorTypes: ['sli.kql.custom', 'sli.apm.transactionDuration'] },
|
||||
DEFAULT_SORTING,
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [SOME_SLO],
|
||||
});
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
filter: `(slo.attributes.indicator.type: sli.kql.custom or slo.attributes.indicator.type: sli.apm.transactionDuration)`,
|
||||
search: '*latency*',
|
||||
searchFields: ['name'],
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not include the search or filter when no criteria provided', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
const result = await repository.find({}, DEFAULT_SORTING, DEFAULT_PAGINATION);
|
||||
|
||||
expect(result).toEqual({
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
total: 1,
|
||||
results: [SOME_SLO],
|
||||
});
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
search: undefined,
|
||||
searchFields: undefined,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts by creation time ascending', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
await repository.find({}, DEFAULT_SORTING, DEFAULT_PAGINATION);
|
||||
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
search: undefined,
|
||||
searchFields: undefined,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts by creation time descending', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
await repository.find(
|
||||
{},
|
||||
{ field: SortField.CreationTime, direction: SortDirection.Desc },
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
search: undefined,
|
||||
searchFields: undefined,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts by indicator type', async () => {
|
||||
const repository = new KibanaSavedObjectsSLORepository(soClientMock);
|
||||
soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO]));
|
||||
|
||||
await repository.find(
|
||||
{},
|
||||
{ field: SortField.IndicatorType, direction: SortDirection.Asc },
|
||||
DEFAULT_PAGINATION
|
||||
);
|
||||
|
||||
expect(soClientMock.find).toHaveBeenCalledWith({
|
||||
type: SO_SLO_TYPE,
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
search: undefined,
|
||||
searchFields: undefined,
|
||||
sortField: 'indicator.type',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,62 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
|
||||
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
|
||||
import { sloSchema } from '@kbn/slo-schema';
|
||||
|
||||
import { StoredSLO, SLO } from '../../domain/models';
|
||||
import { SO_SLO_TYPE } from '../../saved_objects';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import * as t from 'io-ts';
|
||||
import { SLO, StoredSLO } from '../../domain/models';
|
||||
import { SLOIdConflict, SLONotFound } from '../../errors';
|
||||
|
||||
type ObjectValues<T> = T[keyof T];
|
||||
|
||||
export interface Criteria {
|
||||
name?: string;
|
||||
indicatorTypes?: string[];
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
|
||||
export const SortDirection = {
|
||||
Asc: 'Asc',
|
||||
Desc: 'Desc',
|
||||
} as const;
|
||||
|
||||
type SortDirection = ObjectValues<typeof SortDirection>;
|
||||
|
||||
export const SortField = {
|
||||
CreationTime: 'CreationTime',
|
||||
IndicatorType: 'IndicatorType',
|
||||
};
|
||||
|
||||
type SortField = ObjectValues<typeof SortField>;
|
||||
|
||||
export interface Sort {
|
||||
field: SortField;
|
||||
direction: SortDirection;
|
||||
}
|
||||
|
||||
export interface Paginated<T> {
|
||||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
results: T[];
|
||||
}
|
||||
import { SO_SLO_TYPE } from '../../saved_objects';
|
||||
|
||||
export interface SLORepository {
|
||||
save(slo: SLO, options?: { throwOnConflict: boolean }): Promise<SLO>;
|
||||
findAllByIds(ids: string[]): Promise<SLO[]>;
|
||||
findById(id: string): Promise<SLO>;
|
||||
deleteById(id: string): Promise<void>;
|
||||
find(criteria: Criteria, sort: Sort, pagination: Pagination): Promise<Paginated<SLO>>;
|
||||
}
|
||||
|
||||
export class KibanaSavedObjectsSLORepository implements SLORepository {
|
||||
|
@ -120,29 +79,6 @@ export class KibanaSavedObjectsSLORepository implements SLORepository {
|
|||
await this.soClient.delete(SO_SLO_TYPE, response.saved_objects[0].id);
|
||||
}
|
||||
|
||||
async find(criteria: Criteria, sort: Sort, pagination: Pagination): Promise<Paginated<SLO>> {
|
||||
const { search, searchFields } = buildSearch(criteria);
|
||||
const filterKuery = buildFilterKuery(criteria);
|
||||
const { sortField, sortOrder } = buildSortQuery(sort);
|
||||
const response = await this.soClient.find<StoredSLO>({
|
||||
type: SO_SLO_TYPE,
|
||||
page: pagination.page,
|
||||
perPage: pagination.perPage,
|
||||
search,
|
||||
searchFields,
|
||||
filter: filterKuery,
|
||||
sortField,
|
||||
sortOrder,
|
||||
});
|
||||
|
||||
return {
|
||||
total: response.total,
|
||||
page: response.page,
|
||||
perPage: response.per_page,
|
||||
results: response.saved_objects.map((so) => toSLO(so.attributes)),
|
||||
};
|
||||
}
|
||||
|
||||
async findAllByIds(ids: string[]): Promise<SLO[]> {
|
||||
if (ids.length === 0) return [];
|
||||
|
||||
|
@ -163,47 +99,6 @@ export class KibanaSavedObjectsSLORepository implements SLORepository {
|
|||
}
|
||||
}
|
||||
|
||||
function buildSearch(criteria: Criteria): {
|
||||
search: string | undefined;
|
||||
searchFields: string[] | undefined;
|
||||
} {
|
||||
if (!criteria.name) {
|
||||
return { search: undefined, searchFields: undefined };
|
||||
}
|
||||
|
||||
return { search: addWildcardsIfAbsent(criteria.name), searchFields: ['name'] };
|
||||
}
|
||||
|
||||
function buildFilterKuery(criteria: Criteria): string | undefined {
|
||||
const filters: string[] = [];
|
||||
if (!!criteria.indicatorTypes) {
|
||||
const indicatorTypesFilter: string[] = criteria.indicatorTypes.map(
|
||||
(indicatorType) => `slo.attributes.indicator.type: ${indicatorType}`
|
||||
);
|
||||
filters.push(`(${indicatorTypesFilter.join(' or ')})`);
|
||||
}
|
||||
|
||||
return filters.length > 0 ? filters.join(' and ') : undefined;
|
||||
}
|
||||
|
||||
function buildSortQuery(sort: Sort): { sortField: string; sortOrder: 'asc' | 'desc' } {
|
||||
let sortField: string;
|
||||
switch (sort.field) {
|
||||
case SortField.IndicatorType:
|
||||
sortField = 'indicator.type';
|
||||
break;
|
||||
case SortField.CreationTime:
|
||||
default:
|
||||
sortField = 'created_at';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
sortField,
|
||||
sortOrder: sort.direction === SortDirection.Desc ? 'desc' : 'asc',
|
||||
};
|
||||
}
|
||||
|
||||
function toStoredSLO(slo: SLO): StoredSLO {
|
||||
return sloSchema.encode(slo);
|
||||
}
|
||||
|
@ -216,17 +111,3 @@ function toSLO(storedSLO: StoredSLO): SLO {
|
|||
}, t.identity)
|
||||
);
|
||||
}
|
||||
|
||||
const WILDCARD_CHAR = '*';
|
||||
function addWildcardsIfAbsent(value: string): string {
|
||||
let updatedValue = value;
|
||||
if (updatedValue.substring(0, 1) !== WILDCARD_CHAR) {
|
||||
updatedValue = `${WILDCARD_CHAR}${updatedValue}`;
|
||||
}
|
||||
|
||||
if (value.substring(value.length - 1) !== WILDCARD_CHAR) {
|
||||
updatedValue = `${updatedValue}${WILDCARD_CHAR}`;
|
||||
}
|
||||
|
||||
return updatedValue;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from '../../assets/constants';
|
||||
import { SLOId, Status, Summary } from '../../domain/models';
|
||||
import { getElastichsearchQueryOrThrow } from './transform_generators';
|
||||
|
||||
interface EsSummaryDocument {
|
||||
slo: {
|
||||
id: string;
|
||||
revision: number;
|
||||
};
|
||||
sliValue: number;
|
||||
errorBudgetConsumed: number;
|
||||
errorBudgetRemaining: number;
|
||||
errorBudgetInitial: number;
|
||||
errorBudgetEstimated: boolean;
|
||||
statusCode: number;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
export interface Paginated<T> {
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
results: T[];
|
||||
}
|
||||
|
||||
export interface SLOSummary {
|
||||
id: SLOId;
|
||||
summary: Summary;
|
||||
}
|
||||
|
||||
export type SortField = 'error_budget_consumed' | 'error_budget_remaining' | 'sli_value' | 'status';
|
||||
export interface Sort {
|
||||
field: SortField;
|
||||
direction: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
|
||||
export interface SummarySearchClient {
|
||||
search(kqlQuery: string, sort: Sort, pagination: Pagination): Promise<Paginated<SLOSummary>>;
|
||||
}
|
||||
|
||||
export class DefaultSummarySearchClient implements SummarySearchClient {
|
||||
constructor(private esClient: ElasticsearchClient, private logger: Logger) {}
|
||||
|
||||
async search(
|
||||
kqlQuery: string,
|
||||
sort: Sort,
|
||||
pagination: Pagination
|
||||
): Promise<Paginated<SLOSummary>> {
|
||||
try {
|
||||
const result = await this.esClient.search<EsSummaryDocument>({
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
|
||||
query: getElastichsearchQueryOrThrow(kqlQuery),
|
||||
sort: {
|
||||
[toDocumentSortField(sort.field)]: {
|
||||
order: sort.direction,
|
||||
},
|
||||
},
|
||||
from: (pagination.page - 1) * pagination.perPage,
|
||||
size: pagination.perPage,
|
||||
});
|
||||
|
||||
const total =
|
||||
typeof result.hits.total === 'number' ? result.hits.total : result.hits.total?.value;
|
||||
|
||||
if (total === undefined || total === 0) {
|
||||
return { total: 0, perPage: pagination.perPage, page: pagination.page, results: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
total,
|
||||
perPage: pagination.perPage,
|
||||
page: pagination.page,
|
||||
results: result.hits.hits.map((doc) => ({
|
||||
id: doc._source!.slo.id,
|
||||
summary: {
|
||||
errorBudget: {
|
||||
initial: doc._source!.errorBudgetInitial,
|
||||
consumed: doc._source!.errorBudgetConsumed,
|
||||
remaining: doc._source!.errorBudgetRemaining,
|
||||
isEstimated: doc._source!.errorBudgetEstimated,
|
||||
},
|
||||
sliValue: doc._source!.sliValue,
|
||||
status: doc._source!.status,
|
||||
},
|
||||
})),
|
||||
};
|
||||
} catch (err) {
|
||||
this.logger.error(new Error('Summary search query error', { cause: err }));
|
||||
return { total: 0, perPage: pagination.perPage, page: pagination.page, results: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toDocumentSortField(field: SortField) {
|
||||
switch (field) {
|
||||
case 'error_budget_consumed':
|
||||
return 'errorBudgetConsumed';
|
||||
case 'error_budget_remaining':
|
||||
return 'errorBudgetRemaining';
|
||||
case 'status':
|
||||
return 'status';
|
||||
case 'sli_value':
|
||||
return 'sliValue';
|
||||
default:
|
||||
assertNever(field);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ Array [
|
|||
"description": "Summarize every SLO with timeslices budgeting method and a 7 days rolling time window",
|
||||
"dest": Object {
|
||||
"index": ".slo-observability.summary-v2",
|
||||
"pipeline": ".slo-observability.summary.pipeline",
|
||||
},
|
||||
"frequency": "1m",
|
||||
"pivot": Object {
|
||||
|
@ -60,7 +61,7 @@ Array [
|
|||
"script": "if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }",
|
||||
},
|
||||
},
|
||||
"status": Object {
|
||||
"statusCode": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"errorBudgetRemaining": "errorBudgetRemaining",
|
||||
|
@ -79,6 +80,11 @@ Array [
|
|||
},
|
||||
},
|
||||
"group_by": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"terms": Object {
|
||||
"field": "errorBudgetEstimated",
|
||||
},
|
||||
},
|
||||
"service.environment": Object {
|
||||
"terms": Object {
|
||||
"field": "service.environment",
|
||||
|
@ -189,6 +195,12 @@ Array [
|
|||
],
|
||||
},
|
||||
},
|
||||
"runtime_mappings": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"script": "emit(false)",
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sync": Object {
|
||||
"time": Object {
|
||||
|
@ -214,6 +226,7 @@ Array [
|
|||
"description": "Summarize every SLO with timeslices budgeting method and a 30 days rolling time window",
|
||||
"dest": Object {
|
||||
"index": ".slo-observability.summary-v2",
|
||||
"pipeline": ".slo-observability.summary.pipeline",
|
||||
},
|
||||
"frequency": "1m",
|
||||
"pivot": Object {
|
||||
|
@ -262,7 +275,7 @@ Array [
|
|||
"script": "if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }",
|
||||
},
|
||||
},
|
||||
"status": Object {
|
||||
"statusCode": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"errorBudgetRemaining": "errorBudgetRemaining",
|
||||
|
@ -281,6 +294,11 @@ Array [
|
|||
},
|
||||
},
|
||||
"group_by": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"terms": Object {
|
||||
"field": "errorBudgetEstimated",
|
||||
},
|
||||
},
|
||||
"service.environment": Object {
|
||||
"terms": Object {
|
||||
"field": "service.environment",
|
||||
|
@ -391,6 +409,12 @@ Array [
|
|||
],
|
||||
},
|
||||
},
|
||||
"runtime_mappings": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"script": "emit(false)",
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sync": Object {
|
||||
"time": Object {
|
||||
|
@ -416,6 +440,7 @@ Array [
|
|||
"description": "Summarize every SLO with timeslices budgeting method and a 90 days rolling time window",
|
||||
"dest": Object {
|
||||
"index": ".slo-observability.summary-v2",
|
||||
"pipeline": ".slo-observability.summary.pipeline",
|
||||
},
|
||||
"frequency": "1m",
|
||||
"pivot": Object {
|
||||
|
@ -464,7 +489,7 @@ Array [
|
|||
"script": "if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }",
|
||||
},
|
||||
},
|
||||
"status": Object {
|
||||
"statusCode": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"errorBudgetRemaining": "errorBudgetRemaining",
|
||||
|
@ -483,6 +508,11 @@ Array [
|
|||
},
|
||||
},
|
||||
"group_by": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"terms": Object {
|
||||
"field": "errorBudgetEstimated",
|
||||
},
|
||||
},
|
||||
"service.environment": Object {
|
||||
"terms": Object {
|
||||
"field": "service.environment",
|
||||
|
@ -593,6 +623,12 @@ Array [
|
|||
],
|
||||
},
|
||||
},
|
||||
"runtime_mappings": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"script": "emit(false)",
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sync": Object {
|
||||
"time": Object {
|
||||
|
@ -618,6 +654,7 @@ Array [
|
|||
"description": "Summarize every SLO with timeslices budgeting method and a weekly calendar aligned time window",
|
||||
"dest": Object {
|
||||
"index": ".slo-observability.summary-v2",
|
||||
"pipeline": ".slo-observability.summary.pipeline",
|
||||
},
|
||||
"frequency": "1m",
|
||||
"pivot": Object {
|
||||
|
@ -681,7 +718,7 @@ Array [
|
|||
"script": "if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }",
|
||||
},
|
||||
},
|
||||
"status": Object {
|
||||
"statusCode": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"errorBudgetRemaining": "errorBudgetRemaining",
|
||||
|
@ -698,6 +735,11 @@ Array [
|
|||
},
|
||||
},
|
||||
"group_by": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"terms": Object {
|
||||
"field": "errorBudgetEstimated",
|
||||
},
|
||||
},
|
||||
"service.environment": Object {
|
||||
"terms": Object {
|
||||
"field": "service.environment",
|
||||
|
@ -808,6 +850,12 @@ Array [
|
|||
],
|
||||
},
|
||||
},
|
||||
"runtime_mappings": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"script": "emit(false)",
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sync": Object {
|
||||
"time": Object {
|
||||
|
@ -833,6 +881,7 @@ Array [
|
|||
"description": "Summarize every SLO with timeslices budgeting method and a monthly calendar aligned time window",
|
||||
"dest": Object {
|
||||
"index": ".slo-observability.summary-v2",
|
||||
"pipeline": ".slo-observability.summary.pipeline",
|
||||
},
|
||||
"frequency": "1m",
|
||||
"pivot": Object {
|
||||
|
@ -911,7 +960,7 @@ Array [
|
|||
"script": "if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }",
|
||||
},
|
||||
},
|
||||
"status": Object {
|
||||
"statusCode": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"errorBudgetRemaining": "errorBudgetRemaining",
|
||||
|
@ -928,6 +977,11 @@ Array [
|
|||
},
|
||||
},
|
||||
"group_by": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"terms": Object {
|
||||
"field": "errorBudgetEstimated",
|
||||
},
|
||||
},
|
||||
"service.environment": Object {
|
||||
"terms": Object {
|
||||
"field": "service.environment",
|
||||
|
@ -1038,6 +1092,12 @@ Array [
|
|||
],
|
||||
},
|
||||
},
|
||||
"runtime_mappings": Object {
|
||||
"errorBudgetEstimated": Object {
|
||||
"script": "emit(false)",
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sync": Object {
|
||||
"time": Object {
|
||||
|
|
|
@ -56,6 +56,11 @@ export const groupBy = {
|
|||
field: 'slo.timeWindow.type',
|
||||
},
|
||||
},
|
||||
errorBudgetEstimated: {
|
||||
terms: {
|
||||
field: 'errorBudgetEstimated',
|
||||
},
|
||||
},
|
||||
// optional fields: only specified for APM indicators. Must include missing_bucket:true
|
||||
'service.name': {
|
||||
terms: {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_OCCURRENCES_30D_ROLLING: TransformPutTransformRequest = {
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-30d-rolling`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_OCCURRENCES_30D_ROLLING: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsummed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -17,10 +18,17 @@ import { groupBy } from './common';
|
|||
export const SUMMARY_OCCURRENCES_7D_ROLLING: TransformPutTransformRequest = {
|
||||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-7d-rolling`,
|
||||
dest: {
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_OCCURRENCES_7D_ROLLING: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsummed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_OCCURRENCES_90D_ROLLING: TransformPutTransformRequest = {
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-90d-rolling`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_OCCURRENCES_90D_ROLLING: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsummed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_OCCURRENCES_MONTHLY_ALIGNED: TransformPutTransformRequest =
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-monthly-aligned`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(true)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_OCCURRENCES_MONTHLY_ALIGNED: TransformPutTransformRequest =
|
|||
script: '1 - params.errorBudgetConsumed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_OCCURRENCES_WEEKLY_ALIGNED: TransformPutTransformRequest =
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-weekly-aligned`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(true)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_OCCURRENCES_WEEKLY_ALIGNED: TransformPutTransformRequest =
|
|||
script: '1 - params.errorBudgetConsumed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_TIMESLICES_30D_ROLLING: TransformPutTransformRequest = {
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-30d-rolling`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_TIMESLICES_30D_ROLLING: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsummed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_TIMESLICES_7D_ROLLING: TransformPutTransformRequest = {
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-7d-rolling`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_TIMESLICES_7D_ROLLING: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsummed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_TIMESLICES_90D_ROLLING: TransformPutTransformRequest = {
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-90d-rolling`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -105,7 +113,7 @@ export const SUMMARY_TIMESLICES_90D_ROLLING: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsummed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_TIMESLICES_MONTHLY_ALIGNED: TransformPutTransformRequest =
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-monthly-aligned`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -135,7 +143,7 @@ export const SUMMARY_TIMESLICES_MONTHLY_ALIGNED: TransformPutTransformRequest =
|
|||
script: '1 - params.errorBudgetConsumed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
SLO_DESTINATION_INDEX_PATTERN,
|
||||
SLO_RESOURCES_VERSION,
|
||||
SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
|
||||
} from '../../../../assets/constants';
|
||||
import { groupBy } from './common';
|
||||
|
@ -18,9 +19,16 @@ export const SUMMARY_TIMESLICES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
|
|||
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-weekly-aligned`,
|
||||
dest: {
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
|
||||
pipeline: SLO_SUMMARY_INGEST_PIPELINE_NAME,
|
||||
},
|
||||
source: {
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
runtime_mappings: {
|
||||
errorBudgetEstimated: {
|
||||
type: 'boolean',
|
||||
script: 'emit(false)',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -120,7 +128,7 @@ export const SUMMARY_TIMESLICES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
|
|||
script: '1 - params.errorBudgetConsumed',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
statusCode: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
sliValue: 'sliValue',
|
||||
|
|
|
@ -27631,11 +27631,8 @@
|
|||
"xpack.observability.slo.list.errorMessage": "Une erreur s'est produite lors du chargement des SLO. Contactez votre administrateur pour obtenir de l'aide.",
|
||||
"xpack.observability.slo.list.errorNotification": "Un problème est survenu lors de la récupération des SLO",
|
||||
"xpack.observability.slo.list.errorTitle": "Impossible de charger les SLO",
|
||||
"xpack.observability.slo.list.indicatorTypeFilter": "Type d’indicateur",
|
||||
"xpack.observability.slo.list.search": "Recherche",
|
||||
"xpack.observability.slo.list.sortBy": "Trier par",
|
||||
"xpack.observability.slo.list.sortBy.creationTime": "Heure de création",
|
||||
"xpack.observability.slo.list.sortBy.indicatorType": "Type d’indicateur",
|
||||
"xpack.observability.slo.rules.actionGroupSelectorLabel": "Groupe d’action",
|
||||
"xpack.observability.slo.rules.addWindowAriaLabel": "Ajouter une fenêtre",
|
||||
"xpack.observability.slo.rules.addWIndowLabel": "Ajouter une fenêtre",
|
||||
|
|
|
@ -27631,11 +27631,9 @@
|
|||
"xpack.observability.slo.list.errorMessage": "SLOオブジェクトの読み込みエラーが発生しました。ヘルプについては、管理者にお問い合わせください。",
|
||||
"xpack.observability.slo.list.errorNotification": "SLOの取得中に問題が発生しました",
|
||||
"xpack.observability.slo.list.errorTitle": "SLOを読み込めません",
|
||||
"xpack.observability.slo.list.indicatorTypeFilter": "インジケータータイプ",
|
||||
"xpack.observability.slo.list.search": "検索",
|
||||
"xpack.observability.slo.list.sortBy": "並べ替え基準",
|
||||
"xpack.observability.slo.list.sortBy.creationTime": "作成時刻",
|
||||
"xpack.observability.slo.list.sortBy.indicatorType": "インジケータータイプ",
|
||||
|
||||
"xpack.observability.slo.rules.actionGroupSelectorLabel": "アクショングループ",
|
||||
"xpack.observability.slo.rules.addWindowAriaLabel": "時間枠を追加",
|
||||
"xpack.observability.slo.rules.addWIndowLabel": "時間枠を追加",
|
||||
|
|
|
@ -27629,11 +27629,8 @@
|
|||
"xpack.observability.slo.list.errorMessage": "加载 SLO 时出错。请联系您的管理员寻求帮助。",
|
||||
"xpack.observability.slo.list.errorNotification": "提取 SLO 时出现问题",
|
||||
"xpack.observability.slo.list.errorTitle": "无法加载 SLO",
|
||||
"xpack.observability.slo.list.indicatorTypeFilter": "指标类型",
|
||||
"xpack.observability.slo.list.search": "搜索",
|
||||
"xpack.observability.slo.list.sortBy": "排序依据",
|
||||
"xpack.observability.slo.list.sortBy.creationTime": "创建时间",
|
||||
"xpack.observability.slo.list.sortBy.indicatorType": "指标类型",
|
||||
"xpack.observability.slo.rules.actionGroupSelectorLabel": "操作组",
|
||||
"xpack.observability.slo.rules.addWindowAriaLabel": "添加窗口",
|
||||
"xpack.observability.slo.rules.addWIndowLabel": "添加窗口",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue