mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[SLO] Implement federated views (#178050)
This commit is contained in:
parent
73079879c8
commit
721d354a13
206 changed files with 3813 additions and 1902 deletions
|
@ -218,6 +218,7 @@ export const HASH_TO_VERSION_MAP = {
|
|||
'siem-ui-timeline-note|28393dfdeb4e4413393eb5f7ec8c5436': '10.0.0',
|
||||
'siem-ui-timeline-pinned-event|293fce142548281599060e07ad2c9ddb': '10.0.0',
|
||||
'siem-ui-timeline|f6739fd4b17646a6c86321a746c247ef': '10.1.0',
|
||||
'slo-settings|3d1b76c39bfb2cc8296b024d73854724': '10.0.0',
|
||||
'slo|dc7f35c0cf07d71bb36f154996fe10c6': '10.1.0',
|
||||
'space|c3aec2a5d4afcb75554fed96411170e1': '10.0.0',
|
||||
'spaces-usage-stats|3d1b76c39bfb2cc8296b024d73854724': '10.0.0',
|
||||
|
|
|
@ -912,6 +912,7 @@
|
|||
"tags",
|
||||
"version"
|
||||
],
|
||||
"slo-settings": [],
|
||||
"space": [
|
||||
"name"
|
||||
],
|
||||
|
|
|
@ -2996,6 +2996,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"slo-settings": {
|
||||
"dynamic": false,
|
||||
"properties": {}
|
||||
},
|
||||
"space": {
|
||||
"dynamic": false,
|
||||
"properties": {
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
import * as t from 'io-ts';
|
||||
import { omitBy, isPlainObject, isEmpty } from 'lodash';
|
||||
import { isLeft } from 'fp-ts/lib/Either';
|
||||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||
import Boom from '@hapi/boom';
|
||||
import { strictKeysRt } from '@kbn/io-ts-utils';
|
||||
import { formatErrors } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { RouteParamsRT } from './typings';
|
||||
|
||||
interface KibanaRequestParams {
|
||||
|
@ -36,7 +36,7 @@ export function decodeRequestParams<T extends RouteParamsRT>(
|
|||
const result = strictKeysRt(paramsRt).decode(paramMap);
|
||||
|
||||
if (isLeft(result)) {
|
||||
throw Boom.badRequest(PathReporter.report(result)[0]);
|
||||
throw Boom.badRequest(formatErrors(result.left).join('|'));
|
||||
}
|
||||
|
||||
return result.right;
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
"@kbn/core-http-request-handler-context-server",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/core-lifecycle-server",
|
||||
"@kbn/logging"
|
||||
"@kbn/logging",
|
||||
"@kbn/securitysolution-io-ts-utils"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -146,6 +146,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"siem-ui-timeline-note": "0a32fb776907f596bedca292b8c646496ae9c57b",
|
||||
"siem-ui-timeline-pinned-event": "082daa3ce647b33873f6abccf340bdfa32057c8d",
|
||||
"slo": "9a9995e4572de1839651c43b5fc4dc8276bb5815",
|
||||
"slo-settings": "f6b5ed339470a6a2cda272bde1750adcf504a11b",
|
||||
"space": "8de4ec513e9bbc6b2f1d635161d850be7747d38e",
|
||||
"spaces-usage-stats": "3abca98713c52af8b30300e386c7779b3025a20e",
|
||||
"synthetics-monitor": "5ceb25b6249bd26902c9b34273c71c3dce06dbea",
|
||||
|
|
|
@ -120,6 +120,7 @@ const previouslyRegisteredTypes = [
|
|||
'siem-ui-timeline-note',
|
||||
'siem-ui-timeline-pinned-event',
|
||||
'slo',
|
||||
'slo-settings',
|
||||
'space',
|
||||
'spaces-usage-stats',
|
||||
'synthetics-monitor',
|
||||
|
|
|
@ -265,6 +265,7 @@ describe('split .kibana index into multiple system indices', () => {
|
|||
"siem-ui-timeline-note",
|
||||
"siem-ui-timeline-pinned-event",
|
||||
"slo",
|
||||
"slo-settings",
|
||||
"space",
|
||||
"spaces-usage-stats",
|
||||
"synthetics-monitor",
|
||||
|
|
|
@ -7,5 +7,4 @@
|
|||
|
||||
export * from './src/schema';
|
||||
export * from './src/rest_specs';
|
||||
export * from './src/models/duration';
|
||||
export * from './src/models/pagination';
|
||||
export * from './src/models';
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export function useErrorBudgetActions() {}
|
||||
export * from './pagination';
|
||||
export * from './duration';
|
21
x-pack/packages/kbn-slo-schema/src/rest_specs/common.ts
Normal file
21
x-pack/packages/kbn-slo-schema/src/rest_specs/common.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import {
|
||||
budgetingMethodSchema,
|
||||
groupSummarySchema,
|
||||
objectiveSchema,
|
||||
timeWindowTypeSchema,
|
||||
} from '../schema';
|
||||
|
||||
type BudgetingMethod = t.OutputOf<typeof budgetingMethodSchema>;
|
||||
type TimeWindowType = t.OutputOf<typeof timeWindowTypeSchema>;
|
||||
type GroupSummary = t.TypeOf<typeof groupSummarySchema>;
|
||||
type Objective = t.OutputOf<typeof objectiveSchema>;
|
||||
|
||||
export type { BudgetingMethod, Objective, TimeWindowType, GroupSummary };
|
|
@ -6,3 +6,6 @@
|
|||
*/
|
||||
|
||||
export * from './slo';
|
||||
export * from './routes';
|
||||
export * from './indicators';
|
||||
export * from './common';
|
||||
|
|
60
x-pack/packages/kbn-slo-schema/src/rest_specs/indicators.ts
Normal file
60
x-pack/packages/kbn-slo-schema/src/rest_specs/indicators.ts
Normal file
|
@ -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 * as t from 'io-ts';
|
||||
import {
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
histogramIndicatorSchema,
|
||||
indicatorSchema,
|
||||
indicatorTypesSchema,
|
||||
kqlCustomIndicatorSchema,
|
||||
kqlWithFiltersSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
querySchema,
|
||||
syntheticsAvailabilityIndicatorSchema,
|
||||
timesliceMetricBasicMetricWithField,
|
||||
timesliceMetricDocCountMetric,
|
||||
timesliceMetricIndicatorSchema,
|
||||
timesliceMetricPercentileMetric,
|
||||
} from '../schema';
|
||||
|
||||
type IndicatorType = t.OutputOf<typeof indicatorTypesSchema>;
|
||||
type Indicator = t.OutputOf<typeof indicatorSchema>;
|
||||
|
||||
type APMTransactionErrorRateIndicator = t.OutputOf<typeof apmTransactionErrorRateIndicatorSchema>;
|
||||
type APMTransactionDurationIndicator = t.OutputOf<typeof apmTransactionDurationIndicatorSchema>;
|
||||
|
||||
type SyntheticsAvailabilityIndicator = t.OutputOf<typeof syntheticsAvailabilityIndicatorSchema>;
|
||||
|
||||
type MetricCustomIndicator = t.OutputOf<typeof metricCustomIndicatorSchema>;
|
||||
type TimesliceMetricIndicator = t.OutputOf<typeof timesliceMetricIndicatorSchema>;
|
||||
type TimesliceMetricBasicMetricWithField = t.OutputOf<typeof timesliceMetricBasicMetricWithField>;
|
||||
type TimesliceMetricDocCountMetric = t.OutputOf<typeof timesliceMetricDocCountMetric>;
|
||||
type TimesclieMetricPercentileMetric = t.OutputOf<typeof timesliceMetricPercentileMetric>;
|
||||
|
||||
type HistogramIndicator = t.OutputOf<typeof histogramIndicatorSchema>;
|
||||
|
||||
type KQLCustomIndicator = t.OutputOf<typeof kqlCustomIndicatorSchema>;
|
||||
type KqlWithFiltersSchema = t.TypeOf<typeof kqlWithFiltersSchema>;
|
||||
type QuerySchema = t.TypeOf<typeof querySchema>;
|
||||
|
||||
export type {
|
||||
APMTransactionDurationIndicator,
|
||||
APMTransactionErrorRateIndicator,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
IndicatorType,
|
||||
Indicator,
|
||||
MetricCustomIndicator,
|
||||
TimesliceMetricIndicator,
|
||||
TimesliceMetricBasicMetricWithField,
|
||||
TimesclieMetricPercentileMetric,
|
||||
TimesliceMetricDocCountMetric,
|
||||
HistogramIndicator,
|
||||
KQLCustomIndicator,
|
||||
KqlWithFiltersSchema,
|
||||
QuerySchema,
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { indicatorSchema, timeWindowSchema } from '../../schema';
|
||||
import { allOrAnyStringOrArray } from '../../schema/common';
|
||||
import {
|
||||
budgetingMethodSchema,
|
||||
objectiveSchema,
|
||||
optionalSettingsSchema,
|
||||
sloIdSchema,
|
||||
tagsSchema,
|
||||
} from '../../schema/slo';
|
||||
|
||||
const createSLOParamsSchema = t.type({
|
||||
body: t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
description: t.string,
|
||||
indicator: indicatorSchema,
|
||||
timeWindow: timeWindowSchema,
|
||||
budgetingMethod: budgetingMethodSchema,
|
||||
objective: objectiveSchema,
|
||||
}),
|
||||
t.partial({
|
||||
id: sloIdSchema,
|
||||
settings: optionalSettingsSchema,
|
||||
tags: tagsSchema,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
revision: t.number,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const createSLOResponseSchema = t.type({
|
||||
id: sloIdSchema,
|
||||
});
|
||||
|
||||
type CreateSLOInput = t.OutputOf<typeof createSLOParamsSchema.props.body>; // Raw payload sent by the frontend
|
||||
type CreateSLOParams = t.TypeOf<typeof createSLOParamsSchema.props.body>; // Parsed payload used by the backend
|
||||
type CreateSLOResponse = t.TypeOf<typeof createSLOResponseSchema>; // Raw response sent to the frontend
|
||||
|
||||
export { createSLOParamsSchema, createSLOResponseSchema };
|
||||
export type { CreateSLOInput, CreateSLOParams, CreateSLOResponse };
|
|
@ -4,10 +4,13 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import * as t from 'io-ts';
|
||||
import { sloIdSchema } from '../../schema/slo';
|
||||
|
||||
export * from './compute_burn_rate';
|
||||
export * from './error_budget';
|
||||
export * from './compute_sli';
|
||||
export * from './compute_summary_status';
|
||||
export * from './date_range';
|
||||
export * from './validate_slo';
|
||||
const deleteSLOParamsSchema = t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
export { deleteSLOParamsSchema };
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { sloIdSchema } from '../../schema/slo';
|
||||
|
||||
const deleteSLOInstancesParamsSchema = t.type({
|
||||
body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })) }),
|
||||
});
|
||||
|
||||
type DeleteSLOInstancesInput = t.OutputOf<typeof deleteSLOInstancesParamsSchema.props.body>;
|
||||
type DeleteSLOInstancesParams = t.TypeOf<typeof deleteSLOInstancesParamsSchema.props.body>;
|
||||
|
||||
export { deleteSLOInstancesParamsSchema };
|
||||
export type { DeleteSLOInstancesInput, DeleteSLOInstancesParams };
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import {
|
||||
budgetingMethodSchema,
|
||||
objectiveSchema,
|
||||
sloIdSchema,
|
||||
timeWindowSchema,
|
||||
} from '../../schema';
|
||||
import {
|
||||
allOrAnyString,
|
||||
allOrAnyStringOrArray,
|
||||
dateType,
|
||||
summarySchema,
|
||||
} from '../../schema/common';
|
||||
|
||||
const fetchHistoricalSummaryParamsSchema = t.type({
|
||||
body: t.type({
|
||||
list: t.array(
|
||||
t.intersection([
|
||||
t.type({
|
||||
sloId: sloIdSchema,
|
||||
instanceId: t.string,
|
||||
timeWindow: timeWindowSchema,
|
||||
budgetingMethod: budgetingMethodSchema,
|
||||
objective: objectiveSchema,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
revision: t.number,
|
||||
}),
|
||||
t.partial({ remoteName: t.string }),
|
||||
])
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const historicalSummarySchema = t.intersection([
|
||||
t.type({
|
||||
date: dateType,
|
||||
}),
|
||||
summarySchema,
|
||||
]);
|
||||
|
||||
const fetchHistoricalSummaryResponseSchema = t.array(
|
||||
t.type({
|
||||
sloId: sloIdSchema,
|
||||
instanceId: allOrAnyString,
|
||||
data: t.array(historicalSummarySchema),
|
||||
})
|
||||
);
|
||||
|
||||
type FetchHistoricalSummaryParams = t.TypeOf<typeof fetchHistoricalSummaryParamsSchema.props.body>;
|
||||
type FetchHistoricalSummaryResponse = t.OutputOf<typeof fetchHistoricalSummaryResponseSchema>;
|
||||
type HistoricalSummaryResponse = t.OutputOf<typeof historicalSummarySchema>;
|
||||
|
||||
export {
|
||||
fetchHistoricalSummaryParamsSchema,
|
||||
fetchHistoricalSummaryResponseSchema,
|
||||
historicalSummarySchema,
|
||||
};
|
||||
export type {
|
||||
FetchHistoricalSummaryParams,
|
||||
FetchHistoricalSummaryResponse,
|
||||
HistoricalSummaryResponse,
|
||||
};
|
40
x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts
Normal file
40
x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { sloWithDataResponseSchema } from '../slo';
|
||||
|
||||
const sortDirectionSchema = t.union([t.literal('asc'), t.literal('desc')]);
|
||||
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({
|
||||
filters: t.string,
|
||||
kqlQuery: t.string,
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
sortBy: sortBySchema,
|
||||
sortDirection: sortDirectionSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
const findSLOResponseSchema = t.type({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.number,
|
||||
results: t.array(sloWithDataResponseSchema),
|
||||
});
|
||||
|
||||
type FindSLOParams = t.TypeOf<typeof findSLOParamsSchema.props.query>;
|
||||
type FindSLOResponse = t.OutputOf<typeof findSLOResponseSchema>;
|
||||
|
||||
export { findSLOParamsSchema, findSLOResponseSchema };
|
||||
export type { FindSLOParams, FindSLOResponse };
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { toBooleanRt } from '@kbn/io-ts-utils/src/to_boolean_rt';
|
||||
import * as t from 'io-ts';
|
||||
import { sloDefinitionSchema } from '../../schema';
|
||||
|
||||
const findSloDefinitionsParamsSchema = t.partial({
|
||||
query: t.partial({
|
||||
search: t.string,
|
||||
includeOutdatedOnly: toBooleanRt,
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
}),
|
||||
});
|
||||
|
||||
const findSloDefinitionsResponseSchema = t.type({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.number,
|
||||
results: t.array(sloDefinitionSchema),
|
||||
});
|
||||
|
||||
type FindSLODefinitionsParams = t.TypeOf<typeof findSloDefinitionsParamsSchema.props.query>;
|
||||
type FindSLODefinitionsResponse = t.OutputOf<typeof findSloDefinitionsResponseSchema>;
|
||||
|
||||
export { findSloDefinitionsParamsSchema, findSloDefinitionsResponseSchema };
|
||||
export type { FindSLODefinitionsParams, FindSLODefinitionsResponse };
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { groupSummarySchema } from '../../schema/common';
|
||||
|
||||
const groupBySchema = t.union([
|
||||
t.literal('ungrouped'),
|
||||
t.literal('slo.tags'),
|
||||
t.literal('status'),
|
||||
t.literal('slo.indicator.type'),
|
||||
t.literal('_index'),
|
||||
]);
|
||||
|
||||
const findSLOGroupsParamsSchema = t.partial({
|
||||
query: t.partial({
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
groupBy: groupBySchema,
|
||||
groupsFilter: t.union([t.array(t.string), t.string]),
|
||||
kqlQuery: t.string,
|
||||
filters: t.string,
|
||||
}),
|
||||
});
|
||||
|
||||
const sloGroupWithSummaryResponseSchema = t.type({
|
||||
group: t.string,
|
||||
groupBy: t.string,
|
||||
summary: groupSummarySchema,
|
||||
});
|
||||
|
||||
const findSLOGroupsResponseSchema = t.type({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.number,
|
||||
results: t.array(sloGroupWithSummaryResponseSchema),
|
||||
});
|
||||
|
||||
type FindSLOGroupsParams = t.TypeOf<typeof findSLOGroupsParamsSchema.props.query>;
|
||||
type FindSLOGroupsResponse = t.OutputOf<typeof findSLOGroupsResponseSchema>;
|
||||
|
||||
export {
|
||||
findSLOGroupsParamsSchema,
|
||||
findSLOGroupsResponseSchema,
|
||||
sloGroupWithSummaryResponseSchema,
|
||||
};
|
||||
export type { FindSLOGroupsParams, FindSLOGroupsResponse };
|
34
x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get.ts
Normal file
34
x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { allOrAnyString } from '../../schema/common';
|
||||
import { sloIdSchema } from '../../schema/slo';
|
||||
import { sloWithDataResponseSchema } from '../slo';
|
||||
|
||||
const getSLOQuerySchema = t.partial({
|
||||
query: t.partial({
|
||||
instanceId: allOrAnyString,
|
||||
remoteName: t.string,
|
||||
}),
|
||||
});
|
||||
|
||||
const getSLOParamsSchema = t.intersection([
|
||||
t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
}),
|
||||
}),
|
||||
getSLOQuerySchema,
|
||||
]);
|
||||
|
||||
const getSLOResponseSchema = sloWithDataResponseSchema;
|
||||
|
||||
type GetSLOParams = t.TypeOf<typeof getSLOQuerySchema.props.query>;
|
||||
type GetSLOResponse = t.OutputOf<typeof getSLOResponseSchema>;
|
||||
|
||||
export { getSLOParamsSchema, getSLOResponseSchema };
|
||||
export type { GetSLOParams, GetSLOResponse };
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { durationType } from '../../schema';
|
||||
import { allOrAnyString } from '../../schema/common';
|
||||
|
||||
const getSLOBurnRatesResponseSchema = t.type({
|
||||
burnRates: t.array(
|
||||
t.type({
|
||||
name: t.string,
|
||||
burnRate: t.number,
|
||||
sli: t.number,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const getSLOBurnRatesParamsSchema = t.type({
|
||||
path: t.type({ id: t.string }),
|
||||
body: t.intersection([
|
||||
t.type({
|
||||
instanceId: allOrAnyString,
|
||||
windows: t.array(
|
||||
t.type({
|
||||
name: t.string,
|
||||
duration: durationType,
|
||||
})
|
||||
),
|
||||
}),
|
||||
t.partial({ remoteName: t.string }),
|
||||
]),
|
||||
});
|
||||
|
||||
type GetSLOBurnRatesResponse = t.OutputOf<typeof getSLOBurnRatesResponseSchema>;
|
||||
|
||||
export { getSLOBurnRatesParamsSchema, getSLOBurnRatesResponseSchema };
|
||||
export type { GetSLOBurnRatesResponse };
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
const getSLOInstancesParamsSchema = t.type({
|
||||
path: t.type({ id: t.string }),
|
||||
});
|
||||
|
||||
const getSLOInstancesResponseSchema = t.type({
|
||||
groupBy: t.union([t.string, t.array(t.string)]),
|
||||
instances: t.array(t.string),
|
||||
});
|
||||
|
||||
type GetSLOInstancesResponse = t.OutputOf<typeof getSLOInstancesResponseSchema>;
|
||||
|
||||
export { getSLOInstancesParamsSchema, getSLOInstancesResponseSchema };
|
||||
export type { GetSLOInstancesResponse };
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { indicatorSchema, objectiveSchema } from '../../schema';
|
||||
import { dateType } from '../../schema/common';
|
||||
|
||||
const getPreviewDataParamsSchema = t.type({
|
||||
body: t.intersection([
|
||||
t.type({
|
||||
indicator: indicatorSchema,
|
||||
range: t.type({
|
||||
start: t.number,
|
||||
end: t.number,
|
||||
}),
|
||||
}),
|
||||
t.partial({
|
||||
objective: objectiveSchema,
|
||||
instanceId: t.string,
|
||||
groupBy: t.string,
|
||||
remoteName: t.string,
|
||||
groupings: t.record(t.string, t.unknown),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const getPreviewDataResponseSchema = t.array(
|
||||
t.intersection([
|
||||
t.type({
|
||||
date: dateType,
|
||||
sliValue: t.number,
|
||||
}),
|
||||
t.partial({
|
||||
events: t.type({
|
||||
good: t.number,
|
||||
bad: t.number,
|
||||
total: t.number,
|
||||
}),
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
type GetPreviewDataParams = t.TypeOf<typeof getPreviewDataParamsSchema.props.body>;
|
||||
type GetPreviewDataResponse = t.OutputOf<typeof getPreviewDataResponseSchema>;
|
||||
|
||||
export { getPreviewDataParamsSchema, getPreviewDataResponseSchema };
|
||||
export type { GetPreviewDataParams, GetPreviewDataResponse };
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './create';
|
||||
export * from './update';
|
||||
export * from './delete';
|
||||
export * from './find';
|
||||
export * from './find_group';
|
||||
export * from './find_definition';
|
||||
export * from './get';
|
||||
export * from './get_burn_rates';
|
||||
export * from './get_instances';
|
||||
export * from './get_preview_data';
|
||||
export * from './reset';
|
||||
export * from './manage';
|
||||
export * from './delete_instance';
|
||||
export * from './fetch_historical_summary';
|
||||
export * from './put_settings';
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { sloIdSchema } from '../../schema/slo';
|
||||
|
||||
const manageSLOParamsSchema = t.type({
|
||||
path: t.type({ id: sloIdSchema }),
|
||||
});
|
||||
|
||||
type ManageSLOParams = t.TypeOf<typeof manageSLOParamsSchema.props.path>;
|
||||
|
||||
export { manageSLOParamsSchema };
|
||||
export type { ManageSLOParams };
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { sloSettingsSchema } from '../../schema/settings';
|
||||
|
||||
const putSLOSettingsParamsSchema = t.type({
|
||||
body: sloSettingsSchema,
|
||||
});
|
||||
|
||||
const putSLOSettingsResponseSchema = sloSettingsSchema;
|
||||
|
||||
type PutSLOSettingsParams = t.TypeOf<typeof putSLOSettingsParamsSchema.props.body>;
|
||||
type PutSLOSettingsResponse = t.OutputOf<typeof putSLOSettingsResponseSchema>;
|
||||
type GetSLOSettingsResponse = t.OutputOf<typeof sloSettingsSchema>;
|
||||
|
||||
export { putSLOSettingsParamsSchema, putSLOSettingsResponseSchema };
|
||||
export type { PutSLOSettingsParams, PutSLOSettingsResponse, GetSLOSettingsResponse };
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { sloDefinitionSchema, sloIdSchema } from '../../schema/slo';
|
||||
|
||||
const resetSLOParamsSchema = t.type({
|
||||
path: t.type({ id: sloIdSchema }),
|
||||
});
|
||||
|
||||
const resetSLOResponseSchema = sloDefinitionSchema;
|
||||
|
||||
type ResetSLOParams = t.TypeOf<typeof resetSLOParamsSchema.props.path>;
|
||||
type ResetSLOResponse = t.OutputOf<typeof resetSLOResponseSchema>;
|
||||
|
||||
export { resetSLOParamsSchema, resetSLOResponseSchema };
|
||||
export type { ResetSLOParams, ResetSLOResponse };
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { indicatorSchema, timeWindowSchema } from '../../schema';
|
||||
import { allOrAnyStringOrArray } from '../../schema/common';
|
||||
import {
|
||||
budgetingMethodSchema,
|
||||
objectiveSchema,
|
||||
optionalSettingsSchema,
|
||||
sloDefinitionSchema,
|
||||
sloIdSchema,
|
||||
tagsSchema,
|
||||
} from '../../schema/slo';
|
||||
|
||||
const updateSLOParamsSchema = t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
}),
|
||||
body: t.partial({
|
||||
name: t.string,
|
||||
description: t.string,
|
||||
indicator: indicatorSchema,
|
||||
timeWindow: timeWindowSchema,
|
||||
budgetingMethod: budgetingMethodSchema,
|
||||
objective: objectiveSchema,
|
||||
settings: optionalSettingsSchema,
|
||||
tags: tagsSchema,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
}),
|
||||
});
|
||||
|
||||
const updateSLOResponseSchema = sloDefinitionSchema;
|
||||
|
||||
type UpdateSLOInput = t.OutputOf<typeof updateSLOParamsSchema.props.body>;
|
||||
type UpdateSLOParams = t.TypeOf<typeof updateSLOParamsSchema.props.body>;
|
||||
type UpdateSLOResponse = t.OutputOf<typeof updateSLOResponseSchema>;
|
||||
|
||||
export { updateSLOParamsSchema, updateSLOResponseSchema };
|
||||
export type { UpdateSLOInput, UpdateSLOParams, UpdateSLOResponse };
|
|
@ -6,428 +6,27 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { toBooleanRt } from '@kbn/io-ts-utils';
|
||||
import {
|
||||
allOrAnyString,
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
syntheticsAvailabilityIndicatorSchema,
|
||||
budgetingMethodSchema,
|
||||
dateType,
|
||||
durationType,
|
||||
groupingsSchema,
|
||||
histogramIndicatorSchema,
|
||||
historicalSummarySchema,
|
||||
indicatorSchema,
|
||||
indicatorTypesSchema,
|
||||
kqlCustomIndicatorSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
metaSchema,
|
||||
timesliceMetricIndicatorSchema,
|
||||
objectiveSchema,
|
||||
optionalSettingsSchema,
|
||||
previewDataSchema,
|
||||
settingsSchema,
|
||||
sloIdSchema,
|
||||
remoteSchema,
|
||||
sloDefinitionSchema,
|
||||
summarySchema,
|
||||
groupSummarySchema,
|
||||
tagsSchema,
|
||||
timeWindowSchema,
|
||||
timeWindowTypeSchema,
|
||||
timesliceMetricBasicMetricWithField,
|
||||
timesliceMetricDocCountMetric,
|
||||
timesliceMetricPercentileMetric,
|
||||
allOrAnyStringOrArray,
|
||||
kqlWithFiltersSchema,
|
||||
querySchema,
|
||||
} from '../schema';
|
||||
|
||||
const createSLOParamsSchema = t.type({
|
||||
body: t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
description: t.string,
|
||||
indicator: indicatorSchema,
|
||||
timeWindow: timeWindowSchema,
|
||||
budgetingMethod: budgetingMethodSchema,
|
||||
objective: objectiveSchema,
|
||||
}),
|
||||
t.partial({
|
||||
id: sloIdSchema,
|
||||
settings: optionalSettingsSchema,
|
||||
tags: tagsSchema,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
revision: t.number,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const createSLOResponseSchema = t.type({
|
||||
id: sloIdSchema,
|
||||
});
|
||||
|
||||
const getPreviewDataParamsSchema = t.type({
|
||||
body: t.intersection([
|
||||
t.type({
|
||||
indicator: indicatorSchema,
|
||||
range: t.type({
|
||||
start: t.number,
|
||||
end: t.number,
|
||||
}),
|
||||
}),
|
||||
t.partial({
|
||||
objective: objectiveSchema,
|
||||
instanceId: t.string,
|
||||
groupBy: t.string,
|
||||
groupings: t.record(t.string, t.unknown),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const getPreviewDataResponseSchema = t.array(previewDataSchema);
|
||||
|
||||
const deleteSLOParamsSchema = t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
const sortDirectionSchema = t.union([t.literal('asc'), t.literal('desc')]);
|
||||
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({
|
||||
filters: t.string,
|
||||
kqlQuery: t.string,
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
sortBy: sortBySchema,
|
||||
sortDirection: sortDirectionSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
const groupBySchema = t.union([
|
||||
t.literal('ungrouped'),
|
||||
t.literal('slo.tags'),
|
||||
t.literal('status'),
|
||||
t.literal('slo.indicator.type'),
|
||||
]);
|
||||
|
||||
const findSLOGroupsParamsSchema = t.partial({
|
||||
query: t.partial({
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
groupBy: groupBySchema,
|
||||
groupsFilter: t.union([t.array(t.string), t.string]),
|
||||
kqlQuery: t.string,
|
||||
filters: t.string,
|
||||
}),
|
||||
});
|
||||
|
||||
const sloResponseSchema = t.intersection([
|
||||
t.type({
|
||||
id: sloIdSchema,
|
||||
name: t.string,
|
||||
description: t.string,
|
||||
indicator: indicatorSchema,
|
||||
timeWindow: timeWindowSchema,
|
||||
budgetingMethod: budgetingMethodSchema,
|
||||
objective: objectiveSchema,
|
||||
revision: t.number,
|
||||
settings: settingsSchema,
|
||||
enabled: t.boolean,
|
||||
tags: tagsSchema,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
createdAt: dateType,
|
||||
updatedAt: dateType,
|
||||
version: t.number,
|
||||
}),
|
||||
const sloWithDataResponseSchema = t.intersection([
|
||||
sloDefinitionSchema,
|
||||
t.type({ summary: summarySchema, groupings: groupingsSchema }),
|
||||
t.partial({
|
||||
instanceId: allOrAnyString,
|
||||
meta: metaSchema,
|
||||
remote: remoteSchema,
|
||||
}),
|
||||
]);
|
||||
|
||||
const sloWithSummaryResponseSchema = t.intersection([
|
||||
sloResponseSchema,
|
||||
t.intersection([
|
||||
t.type({ summary: summarySchema, groupings: groupingsSchema }),
|
||||
t.partial({ meta: metaSchema }),
|
||||
]),
|
||||
]);
|
||||
type SLODefinitionResponse = t.OutputOf<typeof sloDefinitionSchema>;
|
||||
type SLOWithSummaryResponse = t.OutputOf<typeof sloWithDataResponseSchema>;
|
||||
|
||||
const sloGroupWithSummaryResponseSchema = t.type({
|
||||
group: t.string,
|
||||
groupBy: t.string,
|
||||
summary: groupSummarySchema,
|
||||
});
|
||||
|
||||
const getSLOQuerySchema = t.partial({
|
||||
query: t.partial({
|
||||
instanceId: allOrAnyString,
|
||||
}),
|
||||
});
|
||||
const getSLOParamsSchema = t.intersection([
|
||||
t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
}),
|
||||
}),
|
||||
getSLOQuerySchema,
|
||||
]);
|
||||
|
||||
const getSLOResponseSchema = sloWithSummaryResponseSchema;
|
||||
|
||||
const updateSLOParamsSchema = t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
}),
|
||||
body: t.partial({
|
||||
name: t.string,
|
||||
description: t.string,
|
||||
indicator: indicatorSchema,
|
||||
timeWindow: timeWindowSchema,
|
||||
budgetingMethod: budgetingMethodSchema,
|
||||
objective: objectiveSchema,
|
||||
settings: optionalSettingsSchema,
|
||||
tags: tagsSchema,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
}),
|
||||
});
|
||||
|
||||
const manageSLOParamsSchema = t.type({
|
||||
path: t.type({ id: sloIdSchema }),
|
||||
});
|
||||
|
||||
const resetSLOParamsSchema = t.type({
|
||||
path: t.type({ id: sloIdSchema }),
|
||||
});
|
||||
|
||||
const resetSLOResponseSchema = sloResponseSchema;
|
||||
|
||||
const updateSLOResponseSchema = sloResponseSchema;
|
||||
|
||||
const findSLOResponseSchema = t.type({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.number,
|
||||
results: t.array(sloWithSummaryResponseSchema),
|
||||
});
|
||||
|
||||
const findSLOGroupsResponseSchema = t.type({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.number,
|
||||
results: t.array(sloGroupWithSummaryResponseSchema),
|
||||
});
|
||||
|
||||
const deleteSLOInstancesParamsSchema = t.type({
|
||||
body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })) }),
|
||||
});
|
||||
|
||||
const fetchHistoricalSummaryParamsSchema = t.type({
|
||||
body: t.type({
|
||||
list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })),
|
||||
}),
|
||||
});
|
||||
|
||||
const fetchHistoricalSummaryResponseSchema = t.array(
|
||||
t.type({
|
||||
sloId: sloIdSchema,
|
||||
instanceId: allOrAnyString,
|
||||
data: t.array(historicalSummarySchema),
|
||||
})
|
||||
);
|
||||
|
||||
const findSloDefinitionsParamsSchema = t.partial({
|
||||
query: t.partial({
|
||||
search: t.string,
|
||||
includeOutdatedOnly: toBooleanRt,
|
||||
page: t.string,
|
||||
perPage: t.string,
|
||||
}),
|
||||
});
|
||||
|
||||
const findSloDefinitionsResponseSchema = t.type({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.number,
|
||||
results: t.array(sloResponseSchema),
|
||||
});
|
||||
|
||||
const getSLOBurnRatesResponseSchema = t.type({
|
||||
burnRates: t.array(
|
||||
t.type({
|
||||
name: t.string,
|
||||
burnRate: t.number,
|
||||
sli: t.number,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const getSLOBurnRatesParamsSchema = t.type({
|
||||
path: t.type({ id: t.string }),
|
||||
body: t.type({
|
||||
instanceId: allOrAnyString,
|
||||
windows: t.array(
|
||||
t.type({
|
||||
name: t.string,
|
||||
duration: durationType,
|
||||
})
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const getSLOInstancesParamsSchema = t.type({
|
||||
path: t.type({ id: t.string }),
|
||||
});
|
||||
|
||||
const getSLOInstancesResponseSchema = t.type({
|
||||
groupBy: t.union([t.string, t.array(t.string)]),
|
||||
instances: t.array(t.string),
|
||||
});
|
||||
|
||||
type SLOResponse = t.OutputOf<typeof sloResponseSchema>;
|
||||
type SLOWithSummaryResponse = t.OutputOf<typeof sloWithSummaryResponseSchema>;
|
||||
|
||||
type SLOGroupWithSummaryResponse = t.OutputOf<typeof sloGroupWithSummaryResponseSchema>;
|
||||
|
||||
type CreateSLOInput = t.OutputOf<typeof createSLOParamsSchema.props.body>; // Raw payload sent by the frontend
|
||||
type CreateSLOParams = t.TypeOf<typeof createSLOParamsSchema.props.body>; // Parsed payload used by the backend
|
||||
type CreateSLOResponse = t.TypeOf<typeof createSLOResponseSchema>; // Raw response sent to the frontend
|
||||
|
||||
type GetSLOParams = t.TypeOf<typeof getSLOQuerySchema.props.query>;
|
||||
type GetSLOResponse = t.OutputOf<typeof getSLOResponseSchema>;
|
||||
|
||||
type ManageSLOParams = t.TypeOf<typeof manageSLOParamsSchema.props.path>;
|
||||
|
||||
type ResetSLOParams = t.TypeOf<typeof resetSLOParamsSchema.props.path>;
|
||||
type ResetSLOResponse = t.OutputOf<typeof resetSLOResponseSchema>;
|
||||
|
||||
type UpdateSLOInput = t.OutputOf<typeof updateSLOParamsSchema.props.body>;
|
||||
type UpdateSLOParams = t.TypeOf<typeof updateSLOParamsSchema.props.body>;
|
||||
type UpdateSLOResponse = t.OutputOf<typeof updateSLOResponseSchema>;
|
||||
|
||||
type FindSLOParams = t.TypeOf<typeof findSLOParamsSchema.props.query>;
|
||||
type FindSLOResponse = t.OutputOf<typeof findSLOResponseSchema>;
|
||||
|
||||
type FindSLOGroupsParams = t.TypeOf<typeof findSLOGroupsParamsSchema.props.query>;
|
||||
type FindSLOGroupsResponse = t.OutputOf<typeof findSLOGroupsResponseSchema>;
|
||||
|
||||
type DeleteSLOInstancesInput = t.OutputOf<typeof deleteSLOInstancesParamsSchema.props.body>;
|
||||
type DeleteSLOInstancesParams = t.TypeOf<typeof deleteSLOInstancesParamsSchema.props.body>;
|
||||
|
||||
type FetchHistoricalSummaryParams = t.TypeOf<typeof fetchHistoricalSummaryParamsSchema.props.body>;
|
||||
type FetchHistoricalSummaryResponse = t.OutputOf<typeof fetchHistoricalSummaryResponseSchema>;
|
||||
type HistoricalSummaryResponse = t.OutputOf<typeof historicalSummarySchema>;
|
||||
|
||||
type FindSLODefinitionsParams = t.TypeOf<typeof findSloDefinitionsParamsSchema.props.query>;
|
||||
type FindSLODefinitionsResponse = t.OutputOf<typeof findSloDefinitionsResponseSchema>;
|
||||
|
||||
type GetPreviewDataParams = t.TypeOf<typeof getPreviewDataParamsSchema.props.body>;
|
||||
type GetPreviewDataResponse = t.OutputOf<typeof getPreviewDataResponseSchema>;
|
||||
|
||||
type GetSLOInstancesResponse = t.OutputOf<typeof getSLOInstancesResponseSchema>;
|
||||
|
||||
type GetSLOBurnRatesResponse = t.OutputOf<typeof getSLOBurnRatesResponseSchema>;
|
||||
type BudgetingMethod = t.OutputOf<typeof budgetingMethodSchema>;
|
||||
type TimeWindow = t.OutputOf<typeof timeWindowTypeSchema>;
|
||||
type IndicatorType = t.OutputOf<typeof indicatorTypesSchema>;
|
||||
type Indicator = t.OutputOf<typeof indicatorSchema>;
|
||||
type Objective = t.OutputOf<typeof objectiveSchema>;
|
||||
type APMTransactionErrorRateIndicator = t.OutputOf<typeof apmTransactionErrorRateIndicatorSchema>;
|
||||
type APMTransactionDurationIndicator = t.OutputOf<typeof apmTransactionDurationIndicatorSchema>;
|
||||
type SyntheticsAvailabilityIndicator = t.OutputOf<typeof syntheticsAvailabilityIndicatorSchema>;
|
||||
type MetricCustomIndicator = t.OutputOf<typeof metricCustomIndicatorSchema>;
|
||||
type TimesliceMetricIndicator = t.OutputOf<typeof timesliceMetricIndicatorSchema>;
|
||||
type TimesliceMetricBasicMetricWithField = t.OutputOf<typeof timesliceMetricBasicMetricWithField>;
|
||||
type TimesliceMetricDocCountMetric = t.OutputOf<typeof timesliceMetricDocCountMetric>;
|
||||
type TimesclieMetricPercentileMetric = t.OutputOf<typeof timesliceMetricPercentileMetric>;
|
||||
type HistogramIndicator = t.OutputOf<typeof histogramIndicatorSchema>;
|
||||
type KQLCustomIndicator = t.OutputOf<typeof kqlCustomIndicatorSchema>;
|
||||
type GroupSummary = t.TypeOf<typeof groupSummarySchema>;
|
||||
type KqlWithFiltersSchema = t.TypeOf<typeof kqlWithFiltersSchema>;
|
||||
type QuerySchema = t.TypeOf<typeof querySchema>;
|
||||
|
||||
export {
|
||||
createSLOParamsSchema,
|
||||
deleteSLOParamsSchema,
|
||||
deleteSLOInstancesParamsSchema,
|
||||
findSLOParamsSchema,
|
||||
findSLOResponseSchema,
|
||||
findSLOGroupsParamsSchema,
|
||||
findSLOGroupsResponseSchema,
|
||||
getPreviewDataParamsSchema,
|
||||
getPreviewDataResponseSchema,
|
||||
getSLOParamsSchema,
|
||||
getSLOResponseSchema,
|
||||
fetchHistoricalSummaryParamsSchema,
|
||||
fetchHistoricalSummaryResponseSchema,
|
||||
findSloDefinitionsParamsSchema,
|
||||
findSloDefinitionsResponseSchema,
|
||||
manageSLOParamsSchema,
|
||||
resetSLOParamsSchema,
|
||||
resetSLOResponseSchema,
|
||||
sloResponseSchema,
|
||||
sloWithSummaryResponseSchema,
|
||||
sloGroupWithSummaryResponseSchema,
|
||||
updateSLOParamsSchema,
|
||||
updateSLOResponseSchema,
|
||||
getSLOBurnRatesParamsSchema,
|
||||
getSLOBurnRatesResponseSchema,
|
||||
getSLOInstancesParamsSchema,
|
||||
getSLOInstancesResponseSchema,
|
||||
};
|
||||
export type {
|
||||
BudgetingMethod,
|
||||
CreateSLOInput,
|
||||
CreateSLOParams,
|
||||
CreateSLOResponse,
|
||||
DeleteSLOInstancesInput,
|
||||
DeleteSLOInstancesParams,
|
||||
FindSLOParams,
|
||||
FindSLOResponse,
|
||||
FindSLOGroupsParams,
|
||||
FindSLOGroupsResponse,
|
||||
GetPreviewDataParams,
|
||||
GetPreviewDataResponse,
|
||||
GetSLOParams,
|
||||
GetSLOResponse,
|
||||
FetchHistoricalSummaryParams,
|
||||
FetchHistoricalSummaryResponse,
|
||||
HistoricalSummaryResponse,
|
||||
FindSLODefinitionsParams,
|
||||
FindSLODefinitionsResponse,
|
||||
ManageSLOParams,
|
||||
ResetSLOParams,
|
||||
ResetSLOResponse,
|
||||
SLOResponse,
|
||||
SLOWithSummaryResponse,
|
||||
SLOGroupWithSummaryResponse,
|
||||
UpdateSLOInput,
|
||||
UpdateSLOParams,
|
||||
UpdateSLOResponse,
|
||||
APMTransactionDurationIndicator,
|
||||
APMTransactionErrorRateIndicator,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
GetSLOBurnRatesResponse,
|
||||
GetSLOInstancesResponse,
|
||||
IndicatorType,
|
||||
Indicator,
|
||||
Objective,
|
||||
MetricCustomIndicator,
|
||||
TimesliceMetricIndicator,
|
||||
TimesliceMetricBasicMetricWithField,
|
||||
TimesclieMetricPercentileMetric,
|
||||
TimesliceMetricDocCountMetric,
|
||||
HistogramIndicator,
|
||||
KQLCustomIndicator,
|
||||
TimeWindow,
|
||||
GroupSummary,
|
||||
KqlWithFiltersSchema,
|
||||
QuerySchema,
|
||||
};
|
||||
export { sloWithDataResponseSchema };
|
||||
export type { SLODefinitionResponse, SLOWithSummaryResponse };
|
||||
|
|
|
@ -59,6 +59,11 @@ const metaSchema = t.partial({
|
|||
}),
|
||||
});
|
||||
|
||||
const remoteSchema = t.type({
|
||||
remoteName: t.string,
|
||||
kibanaUrl: t.string,
|
||||
});
|
||||
|
||||
const groupSummarySchema = t.type({
|
||||
total: t.number,
|
||||
worst: t.type({
|
||||
|
@ -76,58 +81,8 @@ const groupSummarySchema = t.type({
|
|||
noData: t.number,
|
||||
});
|
||||
|
||||
const historicalSummarySchema = t.intersection([
|
||||
t.type({
|
||||
date: dateType,
|
||||
}),
|
||||
summarySchema,
|
||||
]);
|
||||
|
||||
const previewDataSchema = t.intersection([
|
||||
t.type({
|
||||
date: dateType,
|
||||
sliValue: t.number,
|
||||
}),
|
||||
t.partial({
|
||||
events: t.type({
|
||||
good: t.number,
|
||||
bad: t.number,
|
||||
total: t.number,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
const dateRangeSchema = t.type({ from: dateType, to: dateType });
|
||||
|
||||
const kqlQuerySchema = t.string;
|
||||
|
||||
const kqlWithFiltersSchema = t.type({
|
||||
kqlQuery: t.string,
|
||||
filters: t.array(
|
||||
t.type({
|
||||
meta: t.partial({
|
||||
alias: t.union([t.string, t.null]),
|
||||
disabled: t.boolean,
|
||||
negate: t.boolean,
|
||||
// controlledBy is there to identify who owns the filter
|
||||
controlledBy: t.string,
|
||||
// allows grouping of filters
|
||||
group: t.string,
|
||||
// index and type are optional only because when you create a new filter, there are no defaults
|
||||
index: t.string,
|
||||
isMultiIndex: t.boolean,
|
||||
type: t.string,
|
||||
key: t.string,
|
||||
params: t.any,
|
||||
value: t.string,
|
||||
}),
|
||||
query: t.record(t.string, t.any),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const querySchema = t.union([kqlQuerySchema, kqlWithFiltersSchema]);
|
||||
|
||||
export {
|
||||
ALL_VALUE,
|
||||
allOrAnyString,
|
||||
|
@ -136,13 +91,9 @@ export {
|
|||
dateType,
|
||||
errorBudgetSchema,
|
||||
groupingsSchema,
|
||||
historicalSummarySchema,
|
||||
previewDataSchema,
|
||||
statusSchema,
|
||||
summarySchema,
|
||||
metaSchema,
|
||||
groupSummarySchema,
|
||||
kqlWithFiltersSchema,
|
||||
querySchema,
|
||||
kqlQuerySchema,
|
||||
remoteSchema,
|
||||
};
|
||||
|
|
|
@ -10,3 +10,4 @@ export * from './duration';
|
|||
export * from './indicators';
|
||||
export * from './time_window';
|
||||
export * from './slo';
|
||||
export * from './settings';
|
||||
|
|
|
@ -6,7 +6,36 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { allOrAnyString, dateRangeSchema, querySchema } from './common';
|
||||
import { allOrAnyString, dateRangeSchema } from './common';
|
||||
|
||||
const kqlQuerySchema = t.string;
|
||||
|
||||
const kqlWithFiltersSchema = t.type({
|
||||
kqlQuery: t.string,
|
||||
filters: t.array(
|
||||
t.type({
|
||||
meta: t.partial({
|
||||
alias: t.union([t.string, t.null]),
|
||||
disabled: t.boolean,
|
||||
negate: t.boolean,
|
||||
// controlledBy is there to identify who owns the filter
|
||||
controlledBy: t.string,
|
||||
// allows grouping of filters
|
||||
group: t.string,
|
||||
// index and type are optional only because when you create a new filter, there are no defaults
|
||||
index: t.string,
|
||||
isMultiIndex: t.boolean,
|
||||
type: t.string,
|
||||
key: t.string,
|
||||
params: t.any,
|
||||
value: t.string,
|
||||
}),
|
||||
query: t.record(t.string, t.any),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const querySchema = t.union([kqlQuerySchema, kqlWithFiltersSchema]);
|
||||
|
||||
const apmTransactionDurationIndicatorTypeSchema = t.literal('sli.apm.transactionDuration');
|
||||
const apmTransactionDurationIndicatorSchema = t.type({
|
||||
|
@ -288,6 +317,9 @@ const indicatorSchema = t.union([
|
|||
]);
|
||||
|
||||
export {
|
||||
kqlQuerySchema,
|
||||
kqlWithFiltersSchema,
|
||||
querySchema,
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
apmTransactionDurationIndicatorTypeSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { errorBudgetSchema } from '@kbn/slo-schema';
|
||||
|
||||
type ErrorBudget = t.TypeOf<typeof errorBudgetSchema>;
|
||||
|
||||
export type { ErrorBudget };
|
||||
export const sloSettingsSchema = t.type({
|
||||
useAllRemoteClusters: t.boolean,
|
||||
selectedRemoteClusters: t.array(t.string),
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { allOrAnyStringOrArray, dateType, summarySchema, groupingsSchema } from './common';
|
||||
import { allOrAnyStringOrArray, dateType } from './common';
|
||||
import { durationType } from './duration';
|
||||
import { indicatorSchema } from './indicators';
|
||||
import { timeWindowSchema } from './time_window';
|
||||
|
@ -31,11 +31,13 @@ const settingsSchema = t.type({
|
|||
frequency: durationType,
|
||||
});
|
||||
|
||||
const groupBySchema = allOrAnyStringOrArray;
|
||||
|
||||
const optionalSettingsSchema = t.partial({ ...settingsSchema.props });
|
||||
const tagsSchema = t.array(t.string);
|
||||
const sloIdSchema = t.string;
|
||||
|
||||
const sloSchema = t.type({
|
||||
const sloDefinitionSchema = t.type({
|
||||
id: sloIdSchema,
|
||||
name: t.string,
|
||||
description: t.string,
|
||||
|
@ -49,24 +51,19 @@ const sloSchema = t.type({
|
|||
tags: tagsSchema,
|
||||
createdAt: dateType,
|
||||
updatedAt: dateType,
|
||||
groupBy: allOrAnyStringOrArray,
|
||||
groupBy: groupBySchema,
|
||||
version: t.number,
|
||||
});
|
||||
|
||||
const sloWithSummarySchema = t.intersection([
|
||||
sloSchema,
|
||||
t.type({ summary: summarySchema, groupings: groupingsSchema }),
|
||||
]);
|
||||
|
||||
export {
|
||||
budgetingMethodSchema,
|
||||
objectiveSchema,
|
||||
groupBySchema,
|
||||
occurrencesBudgetingMethodSchema,
|
||||
optionalSettingsSchema,
|
||||
settingsSchema,
|
||||
sloDefinitionSchema,
|
||||
sloIdSchema,
|
||||
sloSchema,
|
||||
sloWithSummarySchema,
|
||||
tagsSchema,
|
||||
targetSchema,
|
||||
timeslicesBudgetingMethodSchema,
|
||||
|
|
|
@ -270,8 +270,7 @@ describe('createApi', () => {
|
|||
expect(response.custom).toHaveBeenCalledWith({
|
||||
body: {
|
||||
attributes: { _inspect: [], data: null },
|
||||
message:
|
||||
'Invalid value 1 supplied to : Partial<{| query: Partial<{| _inspect: pipe(JSON, boolean) |}> |}>/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)',
|
||||
message: 'Invalid value "1" supplied to "query,_inspect"',
|
||||
},
|
||||
statusCode: 400,
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ export const RULES_PATH = '/alerts/rules' as const;
|
|||
export const RULES_LOGS_PATH = '/alerts/rules/logs' as const;
|
||||
export const RULE_DETAIL_PATH = '/alerts/rules/:ruleId' as const;
|
||||
export const CASES_PATH = '/cases' as const;
|
||||
export const SETTINGS_PATH = '/slos/settings' as const;
|
||||
|
||||
// // SLOs have been moved to its own app (slo). Keeping around for redirecting purposes.
|
||||
export const OLD_SLOS_PATH = '/slos' as const;
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
import {
|
||||
dateRangeSchema,
|
||||
historicalSummarySchema,
|
||||
statusSchema,
|
||||
summarySchema,
|
||||
groupingsSchema,
|
||||
groupSummarySchema,
|
||||
metaSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
|
||||
type Status = t.TypeOf<typeof statusSchema>;
|
||||
type DateRange = t.TypeOf<typeof dateRangeSchema>;
|
||||
type HistoricalSummary = t.TypeOf<typeof historicalSummarySchema>;
|
||||
type Summary = t.TypeOf<typeof summarySchema>;
|
||||
type Groupings = t.TypeOf<typeof groupingsSchema>;
|
||||
type Meta = t.TypeOf<typeof metaSchema>;
|
||||
type GroupSummary = t.TypeOf<typeof groupSummarySchema>;
|
||||
|
||||
export type { DateRange, Groupings, GroupSummary, HistoricalSummary, Meta, Status, Summary };
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './common';
|
||||
export { Duration, DurationUnit, toDurationUnit, toMomentUnitOfTime } from '@kbn/slo-schema';
|
||||
export * from './error_budget';
|
||||
export * from './indicators';
|
||||
export * from './slo';
|
||||
export * from './time_window';
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import {
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
syntheticsAvailabilityIndicatorSchema,
|
||||
indicatorDataSchema,
|
||||
indicatorSchema,
|
||||
indicatorTypesSchema,
|
||||
kqlCustomIndicatorSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
|
||||
type APMTransactionErrorRateIndicator = t.TypeOf<typeof apmTransactionErrorRateIndicatorSchema>;
|
||||
type APMTransactionDurationIndicator = t.TypeOf<typeof apmTransactionDurationIndicatorSchema>;
|
||||
type SyntheticsAvailabilityIndicator = t.TypeOf<typeof syntheticsAvailabilityIndicatorSchema>;
|
||||
type KQLCustomIndicator = t.TypeOf<typeof kqlCustomIndicatorSchema>;
|
||||
type MetricCustomIndicator = t.TypeOf<typeof metricCustomIndicatorSchema>;
|
||||
type Indicator = t.TypeOf<typeof indicatorSchema>;
|
||||
type IndicatorTypes = t.TypeOf<typeof indicatorTypesSchema>;
|
||||
type IndicatorData = t.TypeOf<typeof indicatorDataSchema>;
|
||||
|
||||
export type {
|
||||
Indicator,
|
||||
IndicatorTypes,
|
||||
APMTransactionErrorRateIndicator,
|
||||
APMTransactionDurationIndicator,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
KQLCustomIndicator,
|
||||
MetricCustomIndicator,
|
||||
IndicatorData,
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { sloIdSchema, sloSchema, sloWithSummarySchema } from '@kbn/slo-schema';
|
||||
|
||||
type SLO = t.TypeOf<typeof sloSchema>;
|
||||
type SLOId = t.TypeOf<typeof sloIdSchema>;
|
||||
type SLOWithSummary = t.TypeOf<typeof sloWithSummarySchema>;
|
||||
type StoredSLO = t.OutputOf<typeof sloSchema>;
|
||||
|
||||
export type { SLO, SLOWithSummary, SLOId, StoredSLO };
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
calendarAlignedTimeWindowSchema,
|
||||
rollingTimeWindowSchema,
|
||||
timeWindowSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import * as t from 'io-ts';
|
||||
|
||||
type TimeWindow = t.TypeOf<typeof timeWindowSchema>;
|
||||
type RollingTimeWindow = t.TypeOf<typeof rollingTimeWindowSchema>;
|
||||
type CalendarAlignedTimeWindow = t.TypeOf<typeof calendarAlignedTimeWindowSchema>;
|
||||
|
||||
export type { RollingTimeWindow, TimeWindow, CalendarAlignedTimeWindow };
|
||||
|
||||
export function toCalendarAlignedTimeWindowMomentUnit(
|
||||
timeWindow: CalendarAlignedTimeWindow
|
||||
): moment.unitOfTime.StartOf {
|
||||
const unit = timeWindow.duration.unit;
|
||||
switch (unit) {
|
||||
case 'w':
|
||||
return 'isoWeeks';
|
||||
case 'M':
|
||||
return 'months';
|
||||
default:
|
||||
throw new Error(`Invalid calendar aligned time window duration unit: ${unit}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function toRollingTimeWindowMomentUnit(
|
||||
timeWindow: RollingTimeWindow
|
||||
): moment.unitOfTime.Diff {
|
||||
const unit = timeWindow.duration.unit;
|
||||
switch (unit) {
|
||||
case 'd':
|
||||
return 'days';
|
||||
default:
|
||||
throw new Error(`Invalid rolling time window duration unit: ${unit}`);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* 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 { toHighPrecision } from '../../utils/number';
|
||||
import { IndicatorData, SLO } from '../models';
|
||||
|
||||
/**
|
||||
* A Burn Rate is computed with the Indicator Data retrieved from a specific lookback period
|
||||
* It tells how fast we are consumming our error budget during a specific period
|
||||
*/
|
||||
export function computeBurnRate(slo: SLO, sliData: IndicatorData): number {
|
||||
const { good, total } = sliData;
|
||||
if (total === 0 || good >= total) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const errorBudget = 1 - slo.objective.target;
|
||||
const errorRate = 1 - good / total;
|
||||
return toHighPrecision(errorRate / errorBudget);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* 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 { computeSLI } from './compute_sli';
|
||||
|
||||
describe('computeSLI', () => {
|
||||
it('returns -1 when no total events', () => {
|
||||
expect(computeSLI(100, 0)).toEqual(-1);
|
||||
});
|
||||
|
||||
it('returns the sli value', () => {
|
||||
expect(computeSLI(100, 1000)).toEqual(0.1);
|
||||
});
|
||||
|
||||
it('returns when good is greater than total events', () => {
|
||||
expect(computeSLI(9999, 9)).toEqual(1111);
|
||||
});
|
||||
|
||||
it('returns rounds the value to 6 digits', () => {
|
||||
expect(computeSLI(33, 90)).toEqual(0.366667);
|
||||
});
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* 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 { toHighPrecision } from '../../utils/number';
|
||||
|
||||
const NO_DATA = -1;
|
||||
|
||||
export function computeSLI(good: number, total: number): number {
|
||||
if (total === 0) {
|
||||
return NO_DATA;
|
||||
}
|
||||
|
||||
return toHighPrecision(good / total);
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 { ErrorBudget, SLO, Status } from '../models';
|
||||
|
||||
export function computeSummaryStatus(slo: SLO, sliValue: number, errorBudget: ErrorBudget): Status {
|
||||
if (sliValue === -1) {
|
||||
return 'NO_DATA';
|
||||
}
|
||||
|
||||
if (sliValue >= slo.objective.target) {
|
||||
return 'HEALTHY';
|
||||
} else {
|
||||
return errorBudget.remaining > 0 ? 'DEGRADING' : 'VIOLATED';
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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 { calendarAlignedTimeWindowSchema, rollingTimeWindowSchema } from '@kbn/slo-schema';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import moment from 'moment';
|
||||
import { DateRange } from '../models';
|
||||
import {
|
||||
TimeWindow,
|
||||
toCalendarAlignedTimeWindowMomentUnit,
|
||||
toRollingTimeWindowMomentUnit,
|
||||
} from '../models/time_window';
|
||||
|
||||
export const toDateRange = (timeWindow: TimeWindow, currentDate: Date = new Date()): DateRange => {
|
||||
if (calendarAlignedTimeWindowSchema.is(timeWindow)) {
|
||||
const unit = toCalendarAlignedTimeWindowMomentUnit(timeWindow);
|
||||
const from = moment.utc(currentDate).startOf(unit);
|
||||
const to = moment.utc(currentDate).endOf(unit);
|
||||
|
||||
return { from: from.toDate(), to: to.toDate() };
|
||||
}
|
||||
|
||||
if (rollingTimeWindowSchema.is(timeWindow)) {
|
||||
const unit = toRollingTimeWindowMomentUnit(timeWindow);
|
||||
const now = moment.utc(currentDate).startOf('minute');
|
||||
const from = now.clone().subtract(timeWindow.duration.value, unit);
|
||||
const to = now.clone();
|
||||
|
||||
return {
|
||||
from: from.toDate(),
|
||||
to: to.toDate(),
|
||||
};
|
||||
}
|
||||
|
||||
assertNever(timeWindow);
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* 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 { toHighPrecision } from '../../utils/number';
|
||||
import { ErrorBudget } from '../models';
|
||||
|
||||
export function toErrorBudget(
|
||||
initial: number,
|
||||
consumed: number,
|
||||
isEstimated: boolean = false
|
||||
): ErrorBudget {
|
||||
return {
|
||||
initial: toHighPrecision(initial),
|
||||
consumed: toHighPrecision(consumed),
|
||||
remaining: toHighPrecision(1 - consumed),
|
||||
isEstimated,
|
||||
};
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* 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 { timeslicesBudgetingMethodSchema } from '@kbn/slo-schema';
|
||||
import { SLO } from '../models';
|
||||
|
||||
export function getDelayInSecondsFromSLO(slo: SLO) {
|
||||
const fixedInterval = timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)
|
||||
? slo.objective.timesliceWindow!.asSeconds()
|
||||
: 60;
|
||||
const syncDelay = slo.settings.syncDelay.asSeconds();
|
||||
const frequency = slo.settings.frequency.asSeconds();
|
||||
return fixedInterval + syncDelay + frequency;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
import { Duration, toMomentUnitOfTime } from '../models';
|
||||
export function getLookbackDateRange(
|
||||
startedAt: Date,
|
||||
duration: Duration,
|
||||
delayInSeconds = 0
|
||||
): { from: Date; to: Date } {
|
||||
const unit = toMomentUnitOfTime(duration.unit);
|
||||
const now = moment(startedAt).subtract(delayInSeconds, 'seconds').startOf('minute');
|
||||
const from = now.clone().subtract(duration.value, unit).startOf('minute');
|
||||
|
||||
return {
|
||||
from: from.toDate(),
|
||||
to: now.toDate(),
|
||||
};
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
timeslicesBudgetingMethodSchema,
|
||||
Duration,
|
||||
DurationUnit,
|
||||
rollingTimeWindowSchema,
|
||||
calendarAlignedTimeWindowSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { IllegalArgumentError } from '../../errors';
|
||||
import { SLO } from '../models';
|
||||
|
||||
/**
|
||||
* Asserts the SLO is valid from a business invariants point of view.
|
||||
* e.g. a 'target' objective requires a number between ]0, 1]
|
||||
* e.g. a 'timeslices' budgeting method requires an objective's timeslice_target to be defined.
|
||||
*
|
||||
* @param slo {SLO}
|
||||
*/
|
||||
export function validateSLO(slo: SLO) {
|
||||
if (!isValidId(slo.id)) {
|
||||
throw new IllegalArgumentError('Invalid id');
|
||||
}
|
||||
|
||||
if (!isValidTargetNumber(slo.objective.target)) {
|
||||
throw new IllegalArgumentError('Invalid objective.target');
|
||||
}
|
||||
|
||||
if (
|
||||
rollingTimeWindowSchema.is(slo.timeWindow) &&
|
||||
!isValidRollingTimeWindowDuration(slo.timeWindow.duration)
|
||||
) {
|
||||
throw new IllegalArgumentError('Invalid time_window.duration');
|
||||
}
|
||||
|
||||
if (
|
||||
calendarAlignedTimeWindowSchema.is(slo.timeWindow) &&
|
||||
!isValidCalendarAlignedTimeWindowDuration(slo.timeWindow.duration)
|
||||
) {
|
||||
throw new IllegalArgumentError('Invalid time_window.duration');
|
||||
}
|
||||
|
||||
if (timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) {
|
||||
if (
|
||||
slo.objective.timesliceTarget === undefined ||
|
||||
!isValidTargetNumber(slo.objective.timesliceTarget)
|
||||
) {
|
||||
throw new IllegalArgumentError('Invalid objective.timeslice_target');
|
||||
}
|
||||
|
||||
if (
|
||||
slo.objective.timesliceWindow === undefined ||
|
||||
!isValidTimesliceWindowDuration(slo.objective.timesliceWindow, slo.timeWindow.duration)
|
||||
) {
|
||||
throw new IllegalArgumentError('Invalid objective.timeslice_window');
|
||||
}
|
||||
}
|
||||
|
||||
validateSettings(slo);
|
||||
}
|
||||
|
||||
function validateSettings(slo: SLO) {
|
||||
if (!isValidFrequencySettings(slo.settings.frequency)) {
|
||||
throw new IllegalArgumentError('Invalid settings.frequency');
|
||||
}
|
||||
|
||||
if (!isValidSyncDelaySettings(slo.settings.syncDelay)) {
|
||||
throw new IllegalArgumentError('Invalid settings.sync_delay');
|
||||
}
|
||||
}
|
||||
|
||||
function isValidId(id: string): boolean {
|
||||
const MIN_ID_LENGTH = 8;
|
||||
const MAX_ID_LENGTH = 36;
|
||||
return MIN_ID_LENGTH <= id.length && id.length <= MAX_ID_LENGTH;
|
||||
}
|
||||
|
||||
function isValidTargetNumber(value: number): boolean {
|
||||
return value > 0 && value < 1;
|
||||
}
|
||||
|
||||
function isValidRollingTimeWindowDuration(duration: Duration): boolean {
|
||||
// 7, 30 or 90days accepted
|
||||
return duration.unit === DurationUnit.Day && [7, 30, 90].includes(duration.value);
|
||||
}
|
||||
|
||||
function isValidCalendarAlignedTimeWindowDuration(duration: Duration): boolean {
|
||||
// 1 week or 1 month
|
||||
return [DurationUnit.Week, DurationUnit.Month].includes(duration.unit) && duration.value === 1;
|
||||
}
|
||||
|
||||
function isValidTimesliceWindowDuration(timesliceWindow: Duration, timeWindow: Duration): boolean {
|
||||
return (
|
||||
[DurationUnit.Minute, DurationUnit.Hour].includes(timesliceWindow.unit) &&
|
||||
timesliceWindow.isShorterThan(timeWindow)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate that 1 minute <= frequency < 1 hour
|
||||
*/
|
||||
function isValidFrequencySettings(frequency: Duration): boolean {
|
||||
return (
|
||||
frequency.isLongerOrEqualThan(new Duration(1, DurationUnit.Minute)) &&
|
||||
frequency.isShorterThan(new Duration(1, DurationUnit.Hour))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate that 1 minute <= sync_delay < 6 hour
|
||||
*/
|
||||
function isValidSyncDelaySettings(syncDelay: Duration): boolean {
|
||||
return (
|
||||
syncDelay.isLongerOrEqualThan(new Duration(1, DurationUnit.Minute)) &&
|
||||
syncDelay.isShorterThan(new Duration(6, DurationUnit.Hour))
|
||||
);
|
||||
}
|
|
@ -101,3 +101,7 @@ export async function typedSearch<
|
|||
): Promise<ESSearchResponse<DocumentSource, TParams>> {
|
||||
return (await esClient.search(params)) as unknown as ESSearchResponse<DocumentSource, TParams>;
|
||||
}
|
||||
|
||||
export function createEsParams<T extends estypes.SearchRequest>(params: T): T {
|
||||
return params;
|
||||
}
|
||||
|
|
|
@ -12,21 +12,23 @@ export const SLO_DETAIL_PATH = '/:sloId' as const;
|
|||
export const SLO_CREATE_PATH = '/create' as const;
|
||||
export const SLO_EDIT_PATH = '/edit/:sloId' as const;
|
||||
export const SLOS_OUTDATED_DEFINITIONS_PATH = '/outdated-definitions' as const;
|
||||
export const SLO_SETTINGS_PATH = '/settings' as const;
|
||||
|
||||
export const paths = {
|
||||
slos: `${SLOS_BASE_PATH}${SLOS_PATH}`,
|
||||
slosSettings: `${SLOS_BASE_PATH}${SLO_SETTINGS_PATH}`,
|
||||
slosWelcome: `${SLOS_BASE_PATH}${SLOS_WELCOME_PATH}`,
|
||||
slosOutdatedDefinitions: `${SLOS_BASE_PATH}${SLOS_OUTDATED_DEFINITIONS_PATH}`,
|
||||
sloCreate: `${SLOS_BASE_PATH}${SLO_CREATE_PATH}`,
|
||||
sloCreateWithEncodedForm: (encodedParams: string) =>
|
||||
`${SLOS_BASE_PATH}${SLO_CREATE_PATH}?_a=${encodedParams}`,
|
||||
sloEdit: (sloId: string) => `${SLOS_BASE_PATH}${SLOS_PATH}/edit/${encodeURIComponent(sloId)}`,
|
||||
sloEdit: (sloId: string) => `${SLOS_BASE_PATH}/edit/${encodeURIComponent(sloId)}`,
|
||||
sloEditWithEncodedForm: (sloId: string, encodedParams: string) =>
|
||||
`${SLOS_BASE_PATH}${SLOS_PATH}/edit/${encodeURIComponent(sloId)}?_a=${encodedParams}`,
|
||||
sloDetails: (sloId: string, instanceId?: string) =>
|
||||
!!instanceId
|
||||
? `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}?instanceId=${encodeURIComponent(
|
||||
instanceId
|
||||
)}`
|
||||
: `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}`,
|
||||
`${SLOS_BASE_PATH}/edit/${encodeURIComponent(sloId)}?_a=${encodedParams}`,
|
||||
sloDetails: (sloId: string, instanceId?: string, remoteName?: string) => {
|
||||
const qs = new URLSearchParams();
|
||||
if (!!instanceId) qs.append('instanceId', instanceId);
|
||||
if (!!remoteName) qs.append('remoteName', remoteName);
|
||||
return `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}?${qs.toString()}`;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { getListOfSloSummaryIndices } from './summary_indices';
|
||||
import { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from './constants';
|
||||
|
||||
describe('getListOfSloSummaryIndices', () => {
|
||||
it('should return default index if disabled', function () {
|
||||
const settings = {
|
||||
useAllRemoteClusters: false,
|
||||
selectedRemoteClusters: [],
|
||||
};
|
||||
const result = getListOfSloSummaryIndices(settings, []);
|
||||
expect(result).toBe(SLO_SUMMARY_DESTINATION_INDEX_PATTERN);
|
||||
});
|
||||
|
||||
it('should return all remote clusters when enabled', function () {
|
||||
const settings = {
|
||||
useAllRemoteClusters: true,
|
||||
selectedRemoteClusters: [],
|
||||
};
|
||||
const clustersByName = [
|
||||
{ name: 'cluster1', isConnected: true },
|
||||
{ name: 'cluster2', isConnected: true },
|
||||
];
|
||||
const result = getListOfSloSummaryIndices(settings, clustersByName);
|
||||
expect(result).toBe(
|
||||
`${SLO_SUMMARY_DESTINATION_INDEX_PATTERN},cluster1:${SLO_SUMMARY_DESTINATION_INDEX_PATTERN},cluster2:${SLO_SUMMARY_DESTINATION_INDEX_PATTERN}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should return selected when enabled', function () {
|
||||
const settings = {
|
||||
useAllRemoteClusters: false,
|
||||
selectedRemoteClusters: ['cluster1'],
|
||||
};
|
||||
const clustersByName = [
|
||||
{ name: 'cluster1', isConnected: true },
|
||||
{ name: 'cluster2', isConnected: true },
|
||||
];
|
||||
const result = getListOfSloSummaryIndices(settings, clustersByName);
|
||||
expect(result).toBe(
|
||||
`${SLO_SUMMARY_DESTINATION_INDEX_PATTERN},cluster1:${SLO_SUMMARY_DESTINATION_INDEX_PATTERN}`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { GetSLOSettingsResponse } from '@kbn/slo-schema';
|
||||
import { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from './constants';
|
||||
|
||||
export const getListOfSloSummaryIndices = (
|
||||
settings: GetSLOSettingsResponse,
|
||||
clustersByName: Array<{ name: string; isConnected: boolean }>
|
||||
) => {
|
||||
const { useAllRemoteClusters, selectedRemoteClusters } = settings;
|
||||
if (!useAllRemoteClusters && selectedRemoteClusters.length === 0) {
|
||||
return SLO_SUMMARY_DESTINATION_INDEX_PATTERN;
|
||||
}
|
||||
|
||||
const indices: string[] = [SLO_SUMMARY_DESTINATION_INDEX_PATTERN];
|
||||
clustersByName.forEach(({ name, isConnected }) => {
|
||||
if (isConnected && (useAllRemoteClusters || selectedRemoteClusters.includes(name))) {
|
||||
indices.push(`${name}:${SLO_SUMMARY_DESTINATION_INDEX_PATTERN}`);
|
||||
}
|
||||
});
|
||||
|
||||
return indices.join(',');
|
||||
};
|
|
@ -6,15 +6,15 @@
|
|||
*/
|
||||
|
||||
import { EuiBasicTable, EuiSpacer, EuiText, EuiTitle, HorizontalAlignment } from '@elastic/eui';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
import { WindowSchema } from '../../typings';
|
||||
import { toDuration, toMinutes } from '../../utils/slo/duration';
|
||||
|
||||
interface AlertTimeTableProps {
|
||||
slo: SLOResponse;
|
||||
slo: SLODefinitionResponse;
|
||||
windows: WindowSchema[];
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ALL_VALUE, SLOResponse } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
|
||||
import { EuiCallOut, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -33,7 +33,7 @@ export function BurnRateRuleEditor(props: Props) {
|
|||
sloId: ruleParams?.sloId,
|
||||
});
|
||||
|
||||
const [selectedSlo, setSelectedSlo] = useState<SLOResponse | undefined>(undefined);
|
||||
const [selectedSlo, setSelectedSlo] = useState<SLODefinitionResponse | undefined>(undefined);
|
||||
const [windowDefs, setWindowDefs] = useState<WindowSchema[]>(ruleParams?.windows || []);
|
||||
const [dependencies, setDependencies] = useState<Dependency[]>(ruleParams?.dependencies || []);
|
||||
|
||||
|
@ -47,7 +47,7 @@ export function BurnRateRuleEditor(props: Props) {
|
|||
});
|
||||
}, [initialSlo]);
|
||||
|
||||
const onSelectedSlo = (slo: SLOResponse | undefined) => {
|
||||
const onSelectedSlo = (slo: SLODefinitionResponse | undefined) => {
|
||||
setSelectedSlo(slo);
|
||||
setWindowDefs(() => {
|
||||
return createDefaultWindows(slo);
|
||||
|
@ -111,7 +111,7 @@ export function BurnRateRuleEditor(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
function createDefaultWindows(slo: SLOResponse | undefined) {
|
||||
function createDefaultWindows(slo: SLODefinitionResponse | undefined) {
|
||||
const burnRateDefaults = slo ? BURN_RATE_DEFAULTS[slo.timeWindow.duration] : [];
|
||||
return burnRateDefaults.map((partialWindow) => createNewWindow(slo, partialWindow));
|
||||
}
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
|
||||
import { KibanaReactStorybookDecorator } from '@kbn/observability-plugin/public';
|
||||
import { SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { SloSelector as Component } from './slo_selector';
|
||||
|
||||
export default {
|
||||
|
@ -20,7 +19,7 @@ export default {
|
|||
|
||||
const Template: ComponentStory<typeof Component> = () => (
|
||||
// eslint-disable-next-line no-console
|
||||
<Component onSelected={(slo: SLOResponse | undefined) => console.log(slo)} />
|
||||
<Component onSelected={(slo: SLODefinitionResponse | undefined) => console.log(slo)} />
|
||||
);
|
||||
const defaultProps = {};
|
||||
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
|
||||
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
import { SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useFetchSloDefinitions } from '../../hooks/use_fetch_slo_definitions';
|
||||
|
||||
interface Props {
|
||||
initialSlo?: SLOResponse;
|
||||
initialSlo?: SLODefinitionResponse;
|
||||
errors?: string[];
|
||||
onSelected: (slo: SLOResponse | undefined) => void;
|
||||
onSelected: (slo: SLODefinitionResponse | undefined) => void;
|
||||
}
|
||||
|
||||
function SloSelector({ initialSlo, onSelected, errors }: Props) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
EuiTitle,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
import { CreateSLOInput, SLOResponse } from '@kbn/slo-schema';
|
||||
import { CreateSLOInput, SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { v4 } from 'uuid';
|
||||
|
@ -36,7 +36,7 @@ import { WindowResult } from './validation';
|
|||
import { BudgetConsumed } from './budget_consumed';
|
||||
|
||||
interface WindowProps extends WindowSchema {
|
||||
slo?: SLOResponse;
|
||||
slo?: SLODefinitionResponse;
|
||||
onChange: (windowDef: WindowSchema) => void;
|
||||
onDelete: (id: string) => void;
|
||||
disableDelete: boolean;
|
||||
|
@ -53,7 +53,7 @@ const ACTION_GROUP_OPTIONS = [
|
|||
|
||||
export const calculateMaxBurnRateThreshold = (
|
||||
longWindow: Duration,
|
||||
slo?: SLOResponse | CreateSLOInput
|
||||
slo?: SLODefinitionResponse | CreateSLOInput
|
||||
) => {
|
||||
return slo
|
||||
? Math.floor(toMinutes(toDuration(slo.timeWindow.duration)) / toMinutes(longWindow))
|
||||
|
@ -246,7 +246,7 @@ const getErrorBudgetExhaustionText = (
|
|||
},
|
||||
});
|
||||
export const createNewWindow = (
|
||||
slo?: SLOResponse | CreateSLOInput,
|
||||
slo?: SLODefinitionResponse | CreateSLOInput,
|
||||
partialWindow: Partial<WindowSchema> = {}
|
||||
): WindowSchema => {
|
||||
const longWindow = partialWindow.longWindow || { value: 1, unit: 'h' };
|
||||
|
@ -264,7 +264,7 @@ export const createNewWindow = (
|
|||
interface WindowsProps {
|
||||
windows: WindowSchema[];
|
||||
onChange: (windows: WindowSchema[]) => void;
|
||||
slo?: SLOResponse;
|
||||
slo?: SLODefinitionResponse;
|
||||
errors: WindowResult[];
|
||||
totalNumberOfWindows?: number;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHeaderLink, EuiHeaderLinks } from '@elast
|
|||
import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { SLOS_BASE_PATH, SLO_SETTINGS_PATH } from '../../../common/locators/paths';
|
||||
|
||||
export function HeaderMenu(): React.ReactElement | null {
|
||||
const { http, theme } = useKibana().services;
|
||||
|
@ -33,6 +34,15 @@ export function HeaderMenu(): React.ReactElement | null {
|
|||
defaultMessage: 'Add integrations',
|
||||
})}
|
||||
</EuiHeaderLink>
|
||||
<EuiHeaderLink
|
||||
color="primary"
|
||||
href={http.basePath.prepend(`${SLOS_BASE_PATH}${SLO_SETTINGS_PATH}`)}
|
||||
iconType="gear"
|
||||
>
|
||||
{i18n.translate('xpack.slo.headerMenu.settings', {
|
||||
defaultMessage: 'Settings',
|
||||
})}
|
||||
</EuiHeaderLink>
|
||||
</EuiHeaderLinks>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTextColor } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
import { SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { toDuration, toMinutes } from '../../../utils/slo/duration';
|
||||
|
||||
export interface BurnRateParams {
|
||||
slo: SLOResponse;
|
||||
slo: SLODefinitionResponse;
|
||||
threshold: number;
|
||||
burnRate?: number;
|
||||
isLoading?: boolean;
|
||||
|
@ -40,7 +40,7 @@ function getTitleFromStatus(status: Status): string {
|
|||
function getSubtitleFromStatus(
|
||||
status: Status,
|
||||
burnRate: number | undefined = 1,
|
||||
slo: SLOResponse
|
||||
slo: SLODefinitionResponse
|
||||
): string {
|
||||
if (status === 'NO_DATA')
|
||||
return i18n.translate('xpack.slo.burnRate.noDataStatusSubtitle', {
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALL_VALUE, SLOResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
import { getGroupKeysProse } from '../../../utils/slo/groupings';
|
||||
|
||||
export interface SloDeleteConfirmationModalProps {
|
||||
slo: SLOWithSummaryResponse | SLOResponse;
|
||||
slo: SLOWithSummaryResponse | SLODefinitionResponse;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
@ -14,7 +14,7 @@ import { getDelayInSecondsFromSLO } from '../../../utils/slo/get_delay_in_second
|
|||
import { AlertAnnotation, TimeRange, useLensDefinition } from './use_lens_definition';
|
||||
|
||||
interface Props {
|
||||
slo: SLOResponse;
|
||||
slo: SLOWithSummaryResponse;
|
||||
dataTimeRange: TimeRange;
|
||||
threshold: number;
|
||||
alertTimeRange?: TimeRange;
|
||||
|
|
|
@ -9,7 +9,7 @@ import { transparentize, useEuiTheme } from '@elastic/eui';
|
|||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
import { ALL_VALUE, SLOResponse } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SLO_DESTINATION_INDEX_PATTERN } from '../../../../common/constants';
|
||||
|
@ -25,7 +25,7 @@ export interface AlertAnnotation {
|
|||
}
|
||||
|
||||
export function useLensDefinition(
|
||||
slo: SLOResponse,
|
||||
slo: SLOWithSummaryResponse,
|
||||
threshold: number,
|
||||
alertTimeRange?: TimeRange,
|
||||
annotations?: AlertAnnotation[],
|
||||
|
@ -466,7 +466,9 @@ export function useLensDefinition(
|
|||
adHocDataViews: {
|
||||
'32ca1ad4-81c0-4daf-b9d1-07118044bdc5': {
|
||||
id: '32ca1ad4-81c0-4daf-b9d1-07118044bdc5',
|
||||
title: SLO_DESTINATION_INDEX_PATTERN,
|
||||
title: !!slo.remote
|
||||
? `${slo.remote.remoteName}:${SLO_DESTINATION_INDEX_PATTERN}`
|
||||
: SLO_DESTINATION_INDEX_PATTERN,
|
||||
timeFieldName: '@timestamp',
|
||||
sourceFilters: [],
|
||||
fieldFormats: {},
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
|
||||
export interface SloResetConfirmationModalProps {
|
||||
slo: SLOWithSummaryResponse | SLOResponse;
|
||||
slo: SLOWithSummaryResponse | SLODefinitionResponse;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
|
|
@ -33,25 +33,31 @@ export function SloStatusBadge({ slo }: SloStatusProps) {
|
|||
</EuiToolTip>
|
||||
)}
|
||||
{slo.summary.status === 'HEALTHY' && (
|
||||
<EuiBadge color="success">
|
||||
{i18n.translate('xpack.slo.sloStatusBadge.healthy', {
|
||||
defaultMessage: 'Healthy',
|
||||
})}
|
||||
</EuiBadge>
|
||||
<div>
|
||||
<EuiBadge color="success">
|
||||
{i18n.translate('xpack.slo.sloStatusBadge.healthy', {
|
||||
defaultMessage: 'Healthy',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</div>
|
||||
)}
|
||||
{slo.summary.status === 'DEGRADING' && (
|
||||
<EuiBadge color="warning">
|
||||
{i18n.translate('xpack.slo.sloStatusBadge.degrading', {
|
||||
defaultMessage: 'Degrading',
|
||||
})}
|
||||
</EuiBadge>
|
||||
<div>
|
||||
<EuiBadge color="warning">
|
||||
{i18n.translate('xpack.slo.sloStatusBadge.degrading', {
|
||||
defaultMessage: 'Degrading',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</div>
|
||||
)}
|
||||
{slo.summary.status === 'VIOLATED' && (
|
||||
<EuiBadge color="danger">
|
||||
{i18n.translate('xpack.slo.sloStatusBadge.violated', {
|
||||
defaultMessage: 'Violated',
|
||||
})}
|
||||
</EuiBadge>
|
||||
<div>
|
||||
<EuiBadge color="danger">
|
||||
{i18n.translate('xpack.slo.sloStatusBadge.violated', {
|
||||
defaultMessage: 'Violated',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</div>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart, EuiLink } from '@elastic/eu
|
|||
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useFetchSloList } from '../../../hooks/use_fetch_slo_list';
|
||||
import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
|
||||
import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary';
|
||||
import { useFetchSloDetails } from '../../../hooks/use_fetch_slo_details';
|
||||
|
@ -40,9 +41,15 @@ export function SloErrorBudget({
|
|||
};
|
||||
}, [reloadSubject]);
|
||||
|
||||
const kqlQuery = `slo.id:"${sloId}" and slo.instanceId:"${sloInstanceId}"`;
|
||||
|
||||
const { data: sloList } = useFetchSloList({
|
||||
kqlQuery,
|
||||
});
|
||||
|
||||
const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } =
|
||||
useFetchHistoricalSummary({
|
||||
list: [{ sloId: sloId!, instanceId: sloInstanceId ?? ALL_VALUE }],
|
||||
sloList: sloList?.results ?? [],
|
||||
shouldRefetch: false,
|
||||
});
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ function SingleSloConfiguration({ overviewMode, onCreate, onCancel }: SingleConf
|
|||
showAllGroupByInstances,
|
||||
sloId: selectedSlo?.sloId,
|
||||
sloInstanceId: selectedSlo?.sloInstanceId,
|
||||
remoteName: selectedSlo?.remoteName,
|
||||
overviewMode,
|
||||
});
|
||||
|
||||
|
@ -73,14 +74,18 @@ function SingleSloConfiguration({ overviewMode, onCreate, onCancel }: SingleConf
|
|||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem grow>
|
||||
<SloSelector
|
||||
singleSelection={true}
|
||||
hasError={hasError}
|
||||
onSelected={(slo) => {
|
||||
setHasError(slo === undefined);
|
||||
if (slo && 'id' in slo) {
|
||||
setSelectedSlo({ sloId: slo.id, sloInstanceId: slo.instanceId });
|
||||
setSelectedSlo({
|
||||
sloId: slo.id,
|
||||
sloInstanceId: slo.instanceId,
|
||||
remoteName: slo.remote?.remoteName,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -92,8 +92,14 @@ export class SLOEmbeddable extends AbstractEmbeddable<SloEmbeddableInput, Embedd
|
|||
this.node = node;
|
||||
// required for the export feature to work
|
||||
this.node.setAttribute('data-shared-item', '');
|
||||
const { sloId, sloInstanceId, showAllGroupByInstances, overviewMode, groupFilters } =
|
||||
this.getInput();
|
||||
const {
|
||||
sloId,
|
||||
sloInstanceId,
|
||||
showAllGroupByInstances,
|
||||
overviewMode,
|
||||
groupFilters,
|
||||
remoteName,
|
||||
} = this.getInput();
|
||||
const queryClient = new QueryClient();
|
||||
const { observabilityRuleTypeRegistry } = this.deps.observability;
|
||||
const I18nContext = this.deps.i18n.Context;
|
||||
|
@ -149,6 +155,7 @@ export class SLOEmbeddable extends AbstractEmbeddable<SloEmbeddableInput, Embedd
|
|||
sloInstanceId={sloInstanceId}
|
||||
reloadSubject={this.reloadSubject}
|
||||
showAllGroupByInstances={showAllGroupByInstances}
|
||||
remoteName={remoteName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { SingleSloProps } from './types';
|
|||
export function SloOverview({
|
||||
sloId,
|
||||
sloInstanceId,
|
||||
remoteName,
|
||||
onRenderComplete,
|
||||
reloadSubject,
|
||||
}: SingleSloProps) {
|
||||
|
@ -45,6 +46,7 @@ export function SloOverview({
|
|||
isRefetching,
|
||||
} = useFetchSloDetails({
|
||||
sloId,
|
||||
remoteName,
|
||||
instanceId: sloInstanceId,
|
||||
});
|
||||
|
||||
|
@ -57,7 +59,7 @@ export function SloOverview({
|
|||
});
|
||||
|
||||
const { data: historicalSummaries = [] } = useFetchHistoricalSummary({
|
||||
list: slo ? [{ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE }] : [],
|
||||
sloList: slo ? [slo] : [],
|
||||
});
|
||||
|
||||
const [selectedSlo, setSelectedSlo] = useState<SLOWithSummaryResponse | null>(null);
|
||||
|
|
|
@ -96,12 +96,7 @@ export function SloCardChartList({ sloId }: { sloId: string }) {
|
|||
});
|
||||
|
||||
const { data: historicalSummaries = [] } = useFetchHistoricalSummary({
|
||||
list: [
|
||||
{
|
||||
sloId,
|
||||
instanceId: ALL_VALUE,
|
||||
},
|
||||
],
|
||||
sloList: sloList?.results ?? [],
|
||||
});
|
||||
|
||||
const { colors } = useSloCardColor();
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { EmbeddableInput } from '@kbn/embeddable-plugin/public';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
||||
export type OverviewMode = 'single' | 'groups';
|
||||
export type GroupBy = 'slo.tags' | 'status' | 'slo.indicator.type';
|
||||
export interface GroupFilters {
|
||||
|
@ -28,6 +27,9 @@ export type GroupSloProps = EmbeddableSloProps & {
|
|||
};
|
||||
|
||||
export interface EmbeddableSloProps {
|
||||
sloId?: string;
|
||||
sloInstanceId?: string;
|
||||
remoteName?: string;
|
||||
reloadSubject?: Subject<boolean>;
|
||||
onRenderComplete?: () => void;
|
||||
overviewMode?: OverviewMode;
|
||||
|
|
|
@ -10,13 +10,13 @@ import { HEALTHY_ROLLING_SLO, historicalSummaryData } from '../../data/slo/histo
|
|||
import { Params, UseFetchHistoricalSummaryResponse } from '../use_fetch_historical_summary';
|
||||
|
||||
export const useFetchHistoricalSummary = ({
|
||||
list = [],
|
||||
sloList = [],
|
||||
}: Params): UseFetchHistoricalSummaryResponse => {
|
||||
const data: FetchHistoricalSummaryResponse = [];
|
||||
list.forEach(({ sloId, instanceId }) =>
|
||||
sloList.forEach(({ id, instanceId }) =>
|
||||
data.push({
|
||||
sloId,
|
||||
instanceId,
|
||||
sloId: id,
|
||||
instanceId: instanceId!,
|
||||
data: historicalSummaryData.find((datum) => datum.sloId === HEALTHY_ROLLING_SLO)!.data,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALL_VALUE, SLOResponse } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
|
||||
type SLO = Pick<SLOResponse, 'id' | 'instanceId'>;
|
||||
type SLO = Pick<SLOWithSummaryResponse, 'id' | 'instanceId'>;
|
||||
export class ActiveAlerts {
|
||||
private data: Map<string, number> = new Map();
|
||||
|
||||
|
|
|
@ -6,27 +6,31 @@
|
|||
*/
|
||||
|
||||
import { encode } from '@kbn/rison';
|
||||
import { useCallback } from 'react';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
import { useCallback } from 'react';
|
||||
import { paths } from '../../common/locators/paths';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
import { createRemoteSloCloneUrl } from '../utils/slo/remote_slo_urls';
|
||||
import { useSpace } from './use_space';
|
||||
|
||||
export function useCloneSlo() {
|
||||
const {
|
||||
http: { basePath },
|
||||
application: { navigateToUrl },
|
||||
} = useKibana().services;
|
||||
const spaceId = useSpace();
|
||||
|
||||
return useCallback(
|
||||
(slo: SLOWithSummaryResponse) => {
|
||||
navigateToUrl(
|
||||
basePath.prepend(
|
||||
paths.sloCreateWithEncodedForm(
|
||||
encode({ ...slo, name: `[Copy] ${slo.name}`, id: undefined })
|
||||
)
|
||||
)
|
||||
);
|
||||
if (slo.remote) {
|
||||
window.open(createRemoteSloCloneUrl(slo, spaceId), '_blank');
|
||||
} else {
|
||||
const clonePath = paths.sloCreateWithEncodedForm(
|
||||
encode({ ...slo, name: `[Copy] ${slo.name}`, id: undefined })
|
||||
);
|
||||
navigateToUrl(basePath.prepend(clonePath));
|
||||
}
|
||||
},
|
||||
[navigateToUrl, basePath]
|
||||
[navigateToUrl, basePath, spaceId]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { DataView } from '@kbn/data-views-plugin/common';
|
|||
import { useKibana } from '../utils/kibana_react';
|
||||
|
||||
interface UseCreateDataViewProps {
|
||||
indexPatternString: string | undefined;
|
||||
indexPatternString?: string;
|
||||
}
|
||||
|
||||
export function useCreateDataView({ indexPatternString }: UseCreateDataViewProps) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { FetchHistoricalSummaryResponse } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, FetchHistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
import { sloKeys } from './query_key_factory';
|
||||
import { SLO_LONG_REFETCH_INTERVAL } from '../constants';
|
||||
|
@ -21,16 +21,27 @@ export interface UseFetchHistoricalSummaryResponse {
|
|||
}
|
||||
|
||||
export interface Params {
|
||||
list: Array<{ sloId: string; instanceId: string }>;
|
||||
sloList: SLOWithSummaryResponse[];
|
||||
shouldRefetch?: boolean;
|
||||
}
|
||||
|
||||
export function useFetchHistoricalSummary({
|
||||
list = [],
|
||||
sloList = [],
|
||||
shouldRefetch,
|
||||
}: Params): UseFetchHistoricalSummaryResponse {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const list = sloList.map((slo) => ({
|
||||
sloId: slo.id,
|
||||
instanceId: slo.instanceId ?? ALL_VALUE,
|
||||
remoteName: slo.remote?.remoteName,
|
||||
timeWindow: slo.timeWindow,
|
||||
groupBy: slo.groupBy,
|
||||
revision: slo.revision,
|
||||
objective: slo.objective,
|
||||
budgetingMethod: slo.budgetingMethod,
|
||||
}));
|
||||
|
||||
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
|
||||
queryKey: sloKeys.historicalSummary(list),
|
||||
queryFn: async ({ signal }) => {
|
||||
|
|
|
@ -47,7 +47,11 @@ export function useFetchSloBurnRates({
|
|||
const response = await http.post<GetSLOBurnRatesResponse>(
|
||||
`/internal/observability/slos/${slo.id}/_burn_rates`,
|
||||
{
|
||||
body: JSON.stringify({ windows, instanceId: slo.instanceId ?? ALL_VALUE }),
|
||||
body: JSON.stringify({
|
||||
windows,
|
||||
instanceId: slo.instanceId ?? ALL_VALUE,
|
||||
remoteName: slo.remote?.remoteName,
|
||||
}),
|
||||
signal,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -31,10 +31,12 @@ export interface UseFetchSloDetailsResponse {
|
|||
export function useFetchSloDetails({
|
||||
sloId,
|
||||
instanceId,
|
||||
remoteName,
|
||||
shouldRefetch,
|
||||
}: {
|
||||
sloId?: string;
|
||||
instanceId?: string;
|
||||
remoteName?: string | null;
|
||||
shouldRefetch?: boolean;
|
||||
}): UseFetchSloDetailsResponse {
|
||||
const { http } = useKibana().services;
|
||||
|
@ -47,6 +49,7 @@ export function useFetchSloDetails({
|
|||
const response = await http.get<GetSLOResponse>(`/api/observability/slos/${sloId}`, {
|
||||
query: {
|
||||
...(!!instanceId && instanceId !== ALL_VALUE && { instanceId }),
|
||||
...(remoteName && { remoteName }),
|
||||
},
|
||||
signal,
|
||||
});
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { CreateSLOInput, SLOResponse } from '@kbn/slo-schema';
|
||||
import type { CreateSLOInput, SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
|
||||
interface SLOInspectResponse {
|
||||
slo: SLOResponse;
|
||||
slo: SLODefinitionResponse;
|
||||
pipeline: Record<string, any>;
|
||||
rollUpTransform: TransformPutTransformRequest;
|
||||
summaryTransform: TransformPutTransformRequest;
|
||||
|
|
|
@ -26,10 +26,12 @@ export function useGetPreviewData({
|
|||
groupBy,
|
||||
groupings,
|
||||
instanceId,
|
||||
remoteName,
|
||||
}: {
|
||||
isValid: boolean;
|
||||
groupBy?: string;
|
||||
instanceId?: string;
|
||||
remoteName?: string;
|
||||
groupings?: Record<string, unknown>;
|
||||
objective?: Objective;
|
||||
indicator: Indicator;
|
||||
|
@ -49,6 +51,7 @@ export function useGetPreviewData({
|
|||
groupBy,
|
||||
instanceId,
|
||||
groupings,
|
||||
remoteName,
|
||||
...(objective ? { objective } : null),
|
||||
}),
|
||||
signal,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { useState, useEffect } from 'react';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
|
||||
export function useSpace() {
|
||||
const { spaces } = useKibana().services;
|
||||
const [spaceId, setSpaceId] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (spaces) {
|
||||
spaces.getActiveSpace().then((space) => setSpaceId(space.id));
|
||||
}
|
||||
}, [spaces]);
|
||||
|
||||
return spaceId;
|
||||
}
|
|
@ -65,6 +65,7 @@ export function EventsChartPanel({ slo, range }: Props) {
|
|||
indicator: slo.indicator,
|
||||
groupings: slo.groupings,
|
||||
instanceId: slo.instanceId,
|
||||
remoteName: slo.remote?.remoteName,
|
||||
});
|
||||
|
||||
const dateFormat = uiSettings.get('dateFormat');
|
||||
|
|
|
@ -5,25 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiContextMenuItem,
|
||||
EuiContextMenuPanel,
|
||||
EuiIcon,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import type { RulesParams } from '@kbn/observability-plugin/public';
|
||||
import { rulesLocatorID } from '@kbn/observability-plugin/common';
|
||||
import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils';
|
||||
import { sloFeatureId } from '@kbn/observability-plugin/common';
|
||||
import { EditBurnRateRuleFlyout } from '../../slos/components/common/edit_burn_rate_rule_flyout';
|
||||
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
import { SloDeleteConfirmationModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { useCapabilities } from '../../../hooks/use_capabilities';
|
||||
import { useCloneSlo } from '../../../hooks/use_clone_slo';
|
||||
import { useDeleteSlo } from '../../../hooks/use_delete_slo';
|
||||
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
|
||||
import { isApmIndicatorType } from '../../../utils/slo/indicator';
|
||||
import { EditBurnRateRuleFlyout } from '../../slos/components/common/edit_burn_rate_rule_flyout';
|
||||
import { useGetQueryParams } from '../hooks/use_get_query_params';
|
||||
import { useSloActions } from '../hooks/use_slo_actions';
|
||||
|
||||
export interface Props {
|
||||
slo?: SLOWithSummaryResponse;
|
||||
|
@ -34,18 +39,17 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
const {
|
||||
application: { navigateToUrl, capabilities },
|
||||
http: { basePath },
|
||||
share: {
|
||||
url: { locators },
|
||||
},
|
||||
triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout },
|
||||
} = useKibana().services;
|
||||
|
||||
const hasApmReadCapabilities = capabilities.apm.show;
|
||||
const { hasWriteCapabilities } = useCapabilities();
|
||||
|
||||
const { isDeletingSlo, removeDeleteQueryParam } = useGetQueryParams();
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState<boolean>(false);
|
||||
const [isEditRuleFlyoutOpen, setIsEditRuleFlyoutOpen] = useState(false);
|
||||
|
||||
const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
|
||||
|
||||
const { mutate: deleteSlo } = useDeleteSlo();
|
||||
|
@ -59,11 +63,11 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
const handleActionsClick = () => setIsPopoverOpen((value) => !value);
|
||||
const closePopover = () => setIsPopoverOpen(false);
|
||||
|
||||
const handleEdit = () => {
|
||||
if (slo) {
|
||||
navigate(basePath.prepend(paths.sloEdit(slo.id)));
|
||||
useEffect(() => {
|
||||
if (isDeletingSlo) {
|
||||
setDeleteConfirmationModalOpen(true);
|
||||
}
|
||||
};
|
||||
}, [isDeletingSlo]);
|
||||
|
||||
const onCloseRuleFlyout = () => {
|
||||
setRuleFlyoutVisibility(false);
|
||||
|
@ -74,18 +78,12 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
setRuleFlyoutVisibility(true);
|
||||
};
|
||||
|
||||
const handleNavigateToRules = async () => {
|
||||
if (rules.length === 1) {
|
||||
setIsEditRuleFlyoutOpen(true);
|
||||
setIsPopoverOpen(false);
|
||||
} else {
|
||||
const locator = locators.get<RulesParams>(rulesLocatorID);
|
||||
|
||||
if (slo?.id && locator) {
|
||||
locator.navigate({ params: { sloId: slo.id } }, { replace: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
const { handleNavigateToRules, sloEditUrl, remoteDeleteUrl } = useSloActions({
|
||||
slo,
|
||||
rules,
|
||||
setIsEditRuleFlyoutOpen,
|
||||
setIsActionsPopoverOpen: setIsPopoverOpen,
|
||||
});
|
||||
|
||||
const handleNavigateToApm = () => {
|
||||
if (!slo) {
|
||||
|
@ -108,11 +106,16 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
setDeleteConfirmationModalOpen(true);
|
||||
setIsPopoverOpen(false);
|
||||
if (!!remoteDeleteUrl) {
|
||||
window.open(remoteDeleteUrl, '_blank');
|
||||
} else {
|
||||
setDeleteConfirmationModalOpen(true);
|
||||
setIsPopoverOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteCancel = () => {
|
||||
removeDeleteQueryParam();
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
};
|
||||
|
||||
|
@ -128,6 +131,19 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
[navigateToUrl]
|
||||
);
|
||||
|
||||
const isRemote = !!slo?.remote;
|
||||
const hasUndefinedRemoteKibanaUrl = !!slo?.remote && slo?.remote?.kibanaUrl === '';
|
||||
|
||||
const showRemoteLinkIcon = isRemote ? (
|
||||
<EuiIcon
|
||||
type="popout"
|
||||
size="s"
|
||||
css={{
|
||||
marginLeft: '10px',
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
|
@ -140,7 +156,8 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
iconType="arrowDown"
|
||||
iconSize="s"
|
||||
onClick={handleActionsClick}
|
||||
disabled={isLoading || !slo}
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{i18n.translate('xpack.slo.sloDetails.headerControl.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
|
@ -155,21 +172,27 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
items={[
|
||||
<EuiContextMenuItem
|
||||
key="edit"
|
||||
disabled={!hasWriteCapabilities}
|
||||
disabled={!hasWriteCapabilities || hasUndefinedRemoteKibanaUrl}
|
||||
icon="pencil"
|
||||
onClick={handleEdit}
|
||||
href={sloEditUrl}
|
||||
target={isRemote ? '_blank' : undefined}
|
||||
toolTipContent={
|
||||
hasUndefinedRemoteKibanaUrl ? NOT_AVAILABLE_FOR_UNDEFINED_REMOTE_KIBANA_URL : ''
|
||||
}
|
||||
data-test-subj="sloDetailsHeaderControlPopoverEdit"
|
||||
>
|
||||
{i18n.translate('xpack.slo.sloDetails.headerControl.edit', {
|
||||
defaultMessage: 'Edit',
|
||||
})}
|
||||
{showRemoteLinkIcon}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="createBurnRateRule"
|
||||
disabled={!hasWriteCapabilities}
|
||||
disabled={!hasWriteCapabilities || isRemote}
|
||||
icon="bell"
|
||||
onClick={handleOpenRuleFlyout}
|
||||
data-test-subj="sloDetailsHeaderControlPopoverCreateRule"
|
||||
toolTipContent={isRemote ? NOT_AVAILABLE_FOR_REMOTE : ''}
|
||||
>
|
||||
{i18n.translate('xpack.slo.sloDetails.headerControl.createBurnRateRule', {
|
||||
defaultMessage: 'Create new alert rule',
|
||||
|
@ -177,15 +200,19 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="manageRules"
|
||||
disabled={!hasWriteCapabilities}
|
||||
disabled={!hasWriteCapabilities || hasUndefinedRemoteKibanaUrl}
|
||||
icon="gear"
|
||||
onClick={handleNavigateToRules}
|
||||
data-test-subj="sloDetailsHeaderControlPopoverManageRules"
|
||||
toolTipContent={
|
||||
hasUndefinedRemoteKibanaUrl ? NOT_AVAILABLE_FOR_UNDEFINED_REMOTE_KIBANA_URL : ''
|
||||
}
|
||||
>
|
||||
{i18n.translate('xpack.slo.sloDetails.headerControl.manageRules', {
|
||||
defaultMessage: 'Manage burn rate {count, plural, one {rule} other {rules}}',
|
||||
values: { count: rules.length },
|
||||
})}
|
||||
{showRemoteLinkIcon}
|
||||
</EuiContextMenuItem>,
|
||||
]
|
||||
.concat(
|
||||
|
@ -193,9 +220,10 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
<EuiContextMenuItem
|
||||
key="exploreInApm"
|
||||
icon="bullseye"
|
||||
disabled={!hasApmReadCapabilities}
|
||||
disabled={!hasApmReadCapabilities || isRemote}
|
||||
onClick={handleNavigateToApm}
|
||||
data-test-subj="sloDetailsHeaderControlPopoverExploreInApm"
|
||||
toolTipContent={isRemote ? NOT_AVAILABLE_FOR_REMOTE : ''}
|
||||
>
|
||||
{i18n.translate('xpack.slo.sloDetails.headerControl.exploreInApm', {
|
||||
defaultMessage: 'Service details',
|
||||
|
@ -208,25 +236,33 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
.concat(
|
||||
<EuiContextMenuItem
|
||||
key="clone"
|
||||
disabled={!hasWriteCapabilities}
|
||||
disabled={!hasWriteCapabilities || hasUndefinedRemoteKibanaUrl}
|
||||
icon="copy"
|
||||
onClick={handleClone}
|
||||
data-test-subj="sloDetailsHeaderControlPopoverClone"
|
||||
toolTipContent={
|
||||
hasUndefinedRemoteKibanaUrl ? NOT_AVAILABLE_FOR_UNDEFINED_REMOTE_KIBANA_URL : ''
|
||||
}
|
||||
>
|
||||
{i18n.translate('xpack.slo.slo.item.actions.clone', {
|
||||
defaultMessage: 'Clone',
|
||||
})}
|
||||
{showRemoteLinkIcon}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="delete"
|
||||
icon="trash"
|
||||
disabled={!hasWriteCapabilities}
|
||||
disabled={!hasWriteCapabilities || hasUndefinedRemoteKibanaUrl}
|
||||
onClick={handleDelete}
|
||||
data-test-subj="sloDetailsHeaderControlPopoverDelete"
|
||||
toolTipContent={
|
||||
hasUndefinedRemoteKibanaUrl ? NOT_AVAILABLE_FOR_UNDEFINED_REMOTE_KIBANA_URL : ''
|
||||
}
|
||||
>
|
||||
{i18n.translate('xpack.slo.slo.item.actions.delete', {
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
{showRemoteLinkIcon}
|
||||
</EuiContextMenuItem>
|
||||
)}
|
||||
/>
|
||||
|
@ -259,3 +295,14 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const NOT_AVAILABLE_FOR_REMOTE = i18n.translate('xpack.slo.item.actions.notAvailable', {
|
||||
defaultMessage: 'This action is not available for remote SLOs',
|
||||
});
|
||||
|
||||
const NOT_AVAILABLE_FOR_UNDEFINED_REMOTE_KIBANA_URL = i18n.translate(
|
||||
'xpack.slo.item.actions.remoteKibanaUrlUndefined',
|
||||
{
|
||||
defaultMessage: 'This action is not available for remote SLOs with undefined kibanaUrl',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSkeletonText, EuiText } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSkeletonText, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { SloRemoteBadge } from '../../slos/components/badges/slo_remote_badge';
|
||||
import { SLOGroupings } from '../../slos/components/common/slo_groupings';
|
||||
import { SloStatusBadge } from '../../../components/slo/slo_status_badge';
|
||||
|
||||
|
@ -36,6 +37,7 @@ export function HeaderTitle({ isLoading, slo }: Props) {
|
|||
wrap={true}
|
||||
>
|
||||
<SloStatusBadge slo={slo} />
|
||||
<SloRemoteBadge slo={slo} />
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
<strong>
|
||||
|
@ -59,6 +61,7 @@ export function HeaderTitle({ isLoading, slo }: Props) {
|
|||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { BurnRateOption, BurnRates } from '../../../components/slo/burn_rate/burn_rates';
|
||||
|
||||
import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary';
|
||||
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
|
||||
import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
|
||||
|
@ -19,6 +18,7 @@ import { EventsChartPanel } from './events_chart_panel';
|
|||
import { Overview } from './overview/overview';
|
||||
import { SliChartPanel } from './sli_chart_panel';
|
||||
import { SloDetailsAlerts } from './slo_detail_alerts';
|
||||
import { SloRemoteCallout } from './slo_remote_callout';
|
||||
|
||||
export const TAB_ID_URL_PARAM = 'tabId';
|
||||
export const OVERVIEW_TAB_ID = 'overview';
|
||||
|
@ -91,7 +91,7 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
|||
|
||||
const { data: historicalSummaries = [], isLoading: historicalSummaryLoading } =
|
||||
useFetchHistoricalSummary({
|
||||
list: [{ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE }],
|
||||
sloList: [slo],
|
||||
shouldRefetch: isAutoRefreshing,
|
||||
});
|
||||
|
||||
|
@ -125,6 +125,7 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
|||
|
||||
return selectedTabId === OVERVIEW_TAB_ID ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="xl">
|
||||
<SloRemoteCallout slo={slo} />
|
||||
<EuiFlexItem>
|
||||
<Overview slo={slo} />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { createRemoteSloDetailsUrl } from '../../../utils/slo/remote_slo_urls';
|
||||
import { useSpace } from '../../../hooks/use_space';
|
||||
|
||||
export function SloRemoteCallout({ slo }: { slo: SLOWithSummaryResponse }) {
|
||||
const spaceId = useSpace();
|
||||
const sloDetailsUrl = createRemoteSloDetailsUrl(slo, spaceId);
|
||||
|
||||
if (!slo.remote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.slo.sloDetails.headerTitle.calloutMessage', {
|
||||
defaultMessage: 'Remote SLO',
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.slo.sloDetails.headerTitle.calloutDescription"
|
||||
defaultMessage="This is a remote SLO which belongs to another Kibana instance. It is fetched from the remote cluster: {remoteName} with Kibana URL {kibanaUrl}."
|
||||
values={{
|
||||
remoteName: <strong>{slo.remote.remoteName}</strong>,
|
||||
kibanaUrl: (
|
||||
<EuiLink
|
||||
data-test-subj="sloSloRemoteCalloutLink"
|
||||
href={slo.remote.kibanaUrl}
|
||||
target="_blank"
|
||||
>
|
||||
{slo.remote.kibanaUrl}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiButton
|
||||
data-test-subj="o11yHeaderTitleLinkButtonButton"
|
||||
href={sloDetailsUrl}
|
||||
color="primary"
|
||||
target="_blank"
|
||||
iconType="popout"
|
||||
iconSide="right"
|
||||
>
|
||||
{i18n.translate('xpack.slo.headerTitle.linkButtonButtonLabel', {
|
||||
defaultMessage: 'View remote SLO details',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 { ALL_VALUE } from '@kbn/slo-schema';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const INSTANCE_SEARCH_PARAM = 'instanceId';
|
||||
|
||||
export function useGetInstanceIdQueryParam(): string | undefined {
|
||||
const { search } = useLocation();
|
||||
const searchParams = new URLSearchParams(search);
|
||||
|
||||
const instanceId = searchParams.get(INSTANCE_SEARCH_PARAM);
|
||||
|
||||
return !!instanceId && instanceId !== ALL_VALUE ? instanceId : undefined;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { ALL_VALUE } from '@kbn/slo-schema';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const INSTANCE_SEARCH_PARAM = 'instanceId';
|
||||
export const REMOTE_NAME_PARAM = 'remoteName';
|
||||
export const DELETE_SLO = 'delete';
|
||||
|
||||
export function useGetQueryParams() {
|
||||
const { search, pathname } = useLocation();
|
||||
const history = useHistory();
|
||||
const searchParams = new URLSearchParams(search);
|
||||
|
||||
const instanceId = searchParams.get(INSTANCE_SEARCH_PARAM);
|
||||
const remoteName = searchParams.get(REMOTE_NAME_PARAM);
|
||||
const deleteSlo = searchParams.get(DELETE_SLO);
|
||||
|
||||
const removeDeleteQueryParam = useCallback(() => {
|
||||
const qParams = new URLSearchParams(search);
|
||||
|
||||
// remote delete param from url after initial load
|
||||
if (deleteSlo === 'true') {
|
||||
qParams.delete(DELETE_SLO);
|
||||
history.replace({
|
||||
pathname,
|
||||
search: qParams.toString(),
|
||||
});
|
||||
}
|
||||
}, [deleteSlo, history, pathname, search]);
|
||||
|
||||
return {
|
||||
instanceId: !!instanceId && instanceId !== ALL_VALUE ? instanceId : undefined,
|
||||
remoteName,
|
||||
isDeletingSlo: deleteSlo === 'true',
|
||||
removeDeleteQueryParam,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { rulesLocatorID, RulesParams } from '@kbn/observability-plugin/public';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { Rule } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import path from 'path';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
import { useSpace } from '../../../hooks/use_space';
|
||||
import { BurnRateRuleParams } from '../../../typings';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import {
|
||||
createRemoteSloDeleteUrl,
|
||||
createRemoteSloEditUrl,
|
||||
} from '../../../utils/slo/remote_slo_urls';
|
||||
|
||||
export const useSloActions = ({
|
||||
slo,
|
||||
rules,
|
||||
setIsEditRuleFlyoutOpen,
|
||||
setIsActionsPopoverOpen,
|
||||
}: {
|
||||
slo?: SLOWithSummaryResponse;
|
||||
rules?: Array<Rule<BurnRateRuleParams>>;
|
||||
setIsEditRuleFlyoutOpen: (val: boolean) => void;
|
||||
setIsActionsPopoverOpen: (val: boolean) => void;
|
||||
}) => {
|
||||
const {
|
||||
share: {
|
||||
url: { locators },
|
||||
},
|
||||
http,
|
||||
} = useKibana().services;
|
||||
const spaceId = useSpace();
|
||||
|
||||
if (!slo) {
|
||||
return {
|
||||
sloEditUrl: '',
|
||||
handleNavigateToRules: () => {},
|
||||
remoteDeleteUrl: undefined,
|
||||
sloDetailsUrl: '',
|
||||
};
|
||||
}
|
||||
|
||||
const handleNavigateToRules = async () => {
|
||||
if (rules?.length === 1) {
|
||||
// if there is only one rule we can edit inline in flyout
|
||||
setIsEditRuleFlyoutOpen(true);
|
||||
setIsActionsPopoverOpen(false);
|
||||
} else {
|
||||
const locator = locators.get<RulesParams>(rulesLocatorID);
|
||||
if (!locator) return undefined;
|
||||
|
||||
if (slo.remote && slo.remote.kibanaUrl !== '') {
|
||||
const basePath = http.basePath.get(); // "/kibana/s/my-space"
|
||||
const url = await locator.getUrl({ params: { sloId: slo.id } }); // "/kibana/s/my-space/app/rules/123"
|
||||
// since basePath is already included in the locatorUrl, we need to remove it from the start of url
|
||||
const urlWithoutBasePath = url?.replace(basePath, ''); // "/app/rules/123"
|
||||
const spacePath = spaceId !== 'default' ? `/s/${spaceId}` : '';
|
||||
const remoteUrl = new URL(path.join(spacePath, urlWithoutBasePath), slo.remote.kibanaUrl); // "kibanaUrl/s/my-space/app/rules/123"
|
||||
window.open(remoteUrl, '_blank');
|
||||
} else {
|
||||
locator.navigate({ params: { sloId: slo.id } }, { replace: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const detailsUrl = paths.sloDetails(
|
||||
slo.id,
|
||||
![slo.groupBy].flat().includes(ALL_VALUE) && slo.instanceId ? slo.instanceId : undefined,
|
||||
slo.remote?.remoteName
|
||||
);
|
||||
|
||||
const remoteDeleteUrl = createRemoteSloDeleteUrl(slo, spaceId);
|
||||
|
||||
const sloEditUrl = slo.remote
|
||||
? createRemoteSloEditUrl(slo, spaceId)
|
||||
: http.basePath.prepend(paths.sloEdit(slo.id));
|
||||
|
||||
return {
|
||||
sloEditUrl,
|
||||
handleNavigateToRules,
|
||||
remoteDeleteUrl,
|
||||
sloDetailsUrl: http.basePath.prepend(detailsUrl),
|
||||
};
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiNotificationBadge } from '@elastic/eui';
|
||||
import { EuiNotificationBadge, EuiToolTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useFetchActiveAlerts } from '../../../hooks/use_fetch_active_alerts';
|
||||
|
@ -18,8 +18,8 @@ export const useSloDetailsTabs = ({
|
|||
selectedTabId,
|
||||
setSelectedTabId,
|
||||
}: {
|
||||
isAutoRefreshing: boolean;
|
||||
slo?: SLOWithSummaryResponse | null;
|
||||
isAutoRefreshing: boolean;
|
||||
selectedTabId: SloTabId;
|
||||
setSelectedTabId: (val: SloTabId) => void;
|
||||
}) => {
|
||||
|
@ -28,6 +28,8 @@ export const useSloDetailsTabs = ({
|
|||
shouldRefetch: isAutoRefreshing,
|
||||
});
|
||||
|
||||
const isRemote = !!slo?.remote;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: OVERVIEW_TAB_ID,
|
||||
|
@ -40,19 +42,34 @@ export const useSloDetailsTabs = ({
|
|||
},
|
||||
{
|
||||
id: ALERTS_TAB_ID,
|
||||
label: i18n.translate('xpack.slo.sloDetails.tab.alertsLabel', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
label: isRemote ? (
|
||||
<EuiToolTip
|
||||
content={i18n.translate('xpack.slo.sloDetails.tab.alertsDisabledTooltip', {
|
||||
defaultMessage: 'Alerts are not available for remote SLOs',
|
||||
})}
|
||||
position="right"
|
||||
>
|
||||
<>{ALERTS_LABEL}</>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
ALERTS_LABEL
|
||||
),
|
||||
'data-test-subj': 'alertsTab',
|
||||
disabled: Boolean(isRemote),
|
||||
isSelected: selectedTabId === ALERTS_TAB_ID,
|
||||
append: slo ? (
|
||||
<EuiNotificationBadge className="eui-alignCenter" size="m">
|
||||
{(activeAlerts && activeAlerts.get(slo)) ?? 0}
|
||||
</EuiNotificationBadge>
|
||||
) : null,
|
||||
append:
|
||||
slo && !isRemote ? (
|
||||
<EuiNotificationBadge className="eui-alignCenter" size="m">
|
||||
{(activeAlerts && activeAlerts.get(slo)) ?? 0}
|
||||
</EuiNotificationBadge>
|
||||
) : null,
|
||||
onClick: () => setSelectedTabId(ALERTS_TAB_ID),
|
||||
},
|
||||
];
|
||||
|
||||
return { tabs };
|
||||
};
|
||||
|
||||
const ALERTS_LABEL = i18n.translate('xpack.slo.sloDetails.tab.alertsLabel', {
|
||||
defaultMessage: 'Alerts',
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@ import { HeaderControl } from './components/header_control';
|
|||
import { paths } from '../../../common/locators/paths';
|
||||
import type { SloDetailsPathParams } from './types';
|
||||
import { AutoRefreshButton } from '../../components/slo/auto_refresh_button';
|
||||
import { useGetInstanceIdQueryParam } from './hooks/use_get_instance_id_query_param';
|
||||
import { useGetQueryParams } from './hooks/use_get_query_params';
|
||||
import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage';
|
||||
|
||||
export function SloDetailsPage() {
|
||||
|
@ -50,11 +50,12 @@ export function SloDetailsPage() {
|
|||
const hasRightLicense = hasAtLeast('platinum');
|
||||
|
||||
const { sloId } = useParams<SloDetailsPathParams>();
|
||||
const sloInstanceId = useGetInstanceIdQueryParam();
|
||||
const { instanceId: sloInstanceId, remoteName } = useGetQueryParams();
|
||||
const { storeAutoRefreshState, getAutoRefreshState } = useAutoRefreshStorage();
|
||||
const [isAutoRefreshing, setIsAutoRefreshing] = useState(getAutoRefreshState());
|
||||
const { isLoading, data: slo } = useFetchSloDetails({
|
||||
sloId,
|
||||
remoteName,
|
||||
instanceId: sloInstanceId,
|
||||
shouldRefetch: isAutoRefreshing,
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimeWindow } from '@kbn/slo-schema';
|
||||
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';
|
||||
|
@ -46,7 +46,7 @@ export function SloEditFormObjectiveSection() {
|
|||
const timeWindowType = watch('timeWindow.type');
|
||||
const indicator = watch('indicator.type');
|
||||
|
||||
const [timeWindowTypeState, setTimeWindowTypeState] = useState<TimeWindow | undefined>(
|
||||
const [timeWindowTypeState, setTimeWindowTypeState] = useState<TimeWindowType | undefined>(
|
||||
defaultValues?.timeWindow?.type
|
||||
);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
KQLCustomIndicator,
|
||||
MetricCustomIndicator,
|
||||
TimesliceMetricIndicator,
|
||||
TimeWindow,
|
||||
TimeWindowType,
|
||||
} from '@kbn/slo-schema';
|
||||
import {
|
||||
BUDGETING_METHOD_OCCURRENCES,
|
||||
|
@ -78,7 +78,7 @@ export const BUDGETING_METHOD_OPTIONS: Array<{ value: BudgetingMethod; text: str
|
|||
},
|
||||
];
|
||||
|
||||
export const TIMEWINDOW_TYPE_OPTIONS: Array<{ value: TimeWindow; text: string }> = [
|
||||
export const TIMEWINDOW_TYPE_OPTIONS: Array<{ value: TimeWindowType; text: string }> = [
|
||||
{
|
||||
value: 'rolling',
|
||||
text: i18n.translate('xpack.slo.sloEdit.timeWindow.rolling', {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BudgetingMethod, Indicator, TimeWindow } from '@kbn/slo-schema';
|
||||
import { BudgetingMethod, Indicator, TimeWindowType } from '@kbn/slo-schema';
|
||||
|
||||
export interface CreateSLOForm<IndicatorType = Indicator> {
|
||||
name: string;
|
||||
|
@ -13,7 +13,7 @@ export interface CreateSLOForm<IndicatorType = Indicator> {
|
|||
indicator: IndicatorType;
|
||||
timeWindow: {
|
||||
duration: string;
|
||||
type: TimeWindow;
|
||||
type: TimeWindowType;
|
||||
};
|
||||
tags: string[];
|
||||
budgetingMethod: BudgetingMethod;
|
||||
|
|
|
@ -4,19 +4,19 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText, EuiButton } from '@elastic/eui';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
import { SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import React, { useState } from 'react';
|
||||
import { SloDeleteConfirmationModal } from '../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloTimeWindowBadge } from '../slos/components/badges/slo_time_window_badge';
|
||||
import { SloIndicatorTypeBadge } from '../slos/components/badges/slo_indicator_type_badge';
|
||||
import { SloResetConfirmationModal } from '../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { useDeleteSlo } from '../../hooks/use_delete_slo';
|
||||
import { useResetSlo } from '../../hooks/use_reset_slo';
|
||||
import { SloResetConfirmationModal } from '../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { SloIndicatorTypeBadge } from '../slos/components/badges/slo_indicator_type_badge';
|
||||
import { SloTimeWindowBadge } from '../slos/components/badges/slo_time_window_badge';
|
||||
|
||||
interface OutdatedSloProps {
|
||||
slo: SLOResponse;
|
||||
slo: SLODefinitionResponse;
|
||||
onReset: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSwitch,
|
||||
EuiDescribedFormGroup,
|
||||
EuiComboBox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useFetcher } from '@kbn/observability-shared-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useGetSettings } from './use_get_settings';
|
||||
import { usePutSloSettings } from './use_put_slo_settings';
|
||||
|
||||
export function SettingsForm() {
|
||||
const [useAllRemoteClusters, setUseAllRemoteClusters] = useState(false);
|
||||
const [selectedRemoteClusters, setSelectedRemoteClusters] = useState<string[]>([]);
|
||||
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const { data: currentSettings } = useGetSettings();
|
||||
const { mutateAsync: updateSettings } = usePutSloSettings();
|
||||
|
||||
const { data, loading } = useFetcher(() => {
|
||||
return http?.get<Array<{ name: string }>>('/api/remote_clusters');
|
||||
}, [http]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSettings) {
|
||||
setUseAllRemoteClusters(currentSettings.useAllRemoteClusters);
|
||||
setSelectedRemoteClusters(currentSettings.selectedRemoteClusters);
|
||||
}
|
||||
}, [currentSettings]);
|
||||
|
||||
const onSubmit = async () => {
|
||||
updateSettings({
|
||||
settings: {
|
||||
useAllRemoteClusters,
|
||||
selectedRemoteClusters,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiForm component="form">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
{i18n.translate('xpack.slo.settingsForm.h3.sourceSettingsLabel', {
|
||||
defaultMessage: 'Source settings',
|
||||
})}
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<p>
|
||||
{i18n.translate('xpack.slo.settingsForm.p.fetchSlosFromAllLabel', {
|
||||
defaultMessage: 'Fetch SLOs from all remote clusters.',
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.slo.settingsForm.euiFormRow.useAllRemoteClustersLabel', {
|
||||
defaultMessage: 'Use all remote clusters',
|
||||
})}
|
||||
>
|
||||
<EuiSwitch
|
||||
label=""
|
||||
checked={useAllRemoteClusters}
|
||||
onChange={(evt) => setUseAllRemoteClusters(evt.target.checked)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
{i18n.translate('xpack.slo.settingsForm.h3.remoteSettingsLabel', {
|
||||
defaultMessage: 'Remote clusters',
|
||||
})}
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<p>
|
||||
{i18n.translate('xpack.slo.settingsForm.select.fetchSlosFromAllLabel', {
|
||||
defaultMessage: 'Select remote clusters to fetch SLOs from.',
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.slo.settingsForm.euiFormRow.select.selectRemoteClustersLabel',
|
||||
{ defaultMessage: 'Select remote clusters' }
|
||||
)}
|
||||
>
|
||||
<EuiComboBox
|
||||
options={data?.map((cluster) => ({ label: cluster.name, value: cluster.name })) || []}
|
||||
selectedOptions={selectedRemoteClusters.map((cluster) => ({
|
||||
label: cluster,
|
||||
value: cluster,
|
||||
}))}
|
||||
onChange={(sels) => {
|
||||
setSelectedRemoteClusters(sels.map((s) => s.value as string));
|
||||
}}
|
||||
isDisabled={useAllRemoteClusters}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
isLoading={loading}
|
||||
data-test-subj="o11ySettingsFormCancelButton"
|
||||
onClick={() => {
|
||||
setUseAllRemoteClusters(currentSettings?.useAllRemoteClusters || false);
|
||||
setSelectedRemoteClusters(currentSettings?.selectedRemoteClusters || []);
|
||||
}}
|
||||
isDisabled={isEqual(currentSettings, {
|
||||
useAllRemoteClusters,
|
||||
selectedRemoteClusters,
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.slo.settingsForm.euiButtonEmpty.cancelLabel', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={loading}
|
||||
data-test-subj="o11ySettingsFormSaveButton"
|
||||
color="primary"
|
||||
fill
|
||||
onClick={() => onSubmit()}
|
||||
isDisabled={isEqual(currentSettings, {
|
||||
useAllRemoteClusters,
|
||||
selectedRemoteClusters,
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.slo.settingsForm.applyButtonEmptyLabel', {
|
||||
defaultMessage: 'Apply',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiForm>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
import { SettingsForm } from './settings_form';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { paths } from '../../../common/locators/paths';
|
||||
import { HeaderMenu } from '../../components/header_menu/header_menu';
|
||||
|
||||
export function SloSettingsPage() {
|
||||
const {
|
||||
http: { basePath },
|
||||
} = useKibana().services;
|
||||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
|
||||
useBreadcrumbs([
|
||||
{
|
||||
href: basePath.prepend(paths.slosSettings),
|
||||
text: i18n.translate('xpack.slo.breadcrumbs.slosSettingsText', {
|
||||
defaultMessage: 'SLOs Settings',
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<ObservabilityPageTemplate
|
||||
data-test-subj="slosSettingsPage"
|
||||
pageHeader={{
|
||||
pageTitle: i18n.translate('xpack.slo.pageHeader.title.', {
|
||||
defaultMessage: 'SLOs Settings',
|
||||
}),
|
||||
rightSideItems: [],
|
||||
}}
|
||||
>
|
||||
<HeaderMenu />
|
||||
<SettingsForm />
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { GetSLOSettingsResponse } from '@kbn/slo-schema';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
|
||||
export const useGetSettings = () => {
|
||||
const { http } = useKibana().services;
|
||||
const { isLoading, data } = useQuery({
|
||||
queryKey: ['getSloSettings'],
|
||||
queryFn: async ({ signal }) => {
|
||||
try {
|
||||
return http.get<GetSLOSettingsResponse>('/internal/slo/settings', { signal });
|
||||
} catch (error) {
|
||||
return defaultSettings;
|
||||
}
|
||||
},
|
||||
keepPreviousData: true,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return { isLoading, data };
|
||||
};
|
||||
|
||||
const defaultSettings: GetSLOSettingsResponse = {
|
||||
useAllRemoteClusters: false,
|
||||
selectedRemoteClusters: [],
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue