mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SLOs] Add details page history tab (#181150)
## Summary This will allow users to view data beyond default values in details page chart, users will be able to select date range via picker to view historical SLI, Burn rate or Good/Bad Events chart48a6a029
-ad36-4457-b195-4ab2d739fca0 <img width="1726" alt="image" src="c8936443
-5fe4-4925-9a9f-a22c847f32b7"> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
7fc54940fb
commit
151aab5c15
31 changed files with 761 additions and 269 deletions
|
@ -31,7 +31,13 @@ const fetchHistoricalSummaryParamsSchema = t.type({
|
|||
groupBy: allOrAnyStringOrArray,
|
||||
revision: t.number,
|
||||
}),
|
||||
t.partial({ remoteName: t.string }),
|
||||
t.partial({
|
||||
remoteName: t.string,
|
||||
range: t.type({
|
||||
from: t.string,
|
||||
to: t.string,
|
||||
}),
|
||||
}),
|
||||
])
|
||||
),
|
||||
}),
|
||||
|
|
|
@ -81,7 +81,10 @@ const groupSummarySchema = t.type({
|
|||
noData: t.number,
|
||||
});
|
||||
|
||||
const dateRangeSchema = t.type({ from: dateType, to: dateType });
|
||||
const dateRangeSchema = t.type({
|
||||
from: t.union([dateType, t.string]),
|
||||
to: t.union([dateType, t.string]),
|
||||
});
|
||||
|
||||
export {
|
||||
ALL_VALUE,
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const SLOS_BASE_PATH = '/app/slos';
|
||||
export const SLO_PREFIX = '/slos';
|
||||
export const SLOS_PATH = '/' as const;
|
||||
export const SLOS_WELCOME_PATH = '/welcome' as const;
|
||||
export const SLO_DETAIL_PATH = '/:sloId' as const;
|
||||
export const SLO_DETAIL_PATH = '/:sloId/:tabId?' 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;
|
||||
|
@ -25,10 +26,13 @@ export const paths = {
|
|||
sloEdit: (sloId: string) => `${SLOS_BASE_PATH}/edit/${encodeURIComponent(sloId)}`,
|
||||
sloEditWithEncodedForm: (sloId: string, encodedParams: string) =>
|
||||
`${SLOS_BASE_PATH}/edit/${encodeURIComponent(sloId)}?_a=${encodedParams}`,
|
||||
sloDetails: (sloId: string, instanceId?: string, remoteName?: string) => {
|
||||
sloDetails: (sloId: string, instanceId?: string, remoteName?: string, tabId?: string) => {
|
||||
const qs = new URLSearchParams();
|
||||
if (!!instanceId) qs.append('instanceId', instanceId);
|
||||
if (!!instanceId && instanceId !== '*') qs.append('instanceId', instanceId);
|
||||
if (!!remoteName) qs.append('remoteName', remoteName);
|
||||
if (tabId) {
|
||||
return `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}/${tabId}?${qs.toString()}`;
|
||||
}
|
||||
return `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}?${qs.toString()}`;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { SloTabId } from '../../../pages/slo_details/components/slo_details';
|
||||
import { BurnRateOption } from './burn_rates';
|
||||
interface Props {
|
||||
burnRateOption: BurnRateOption;
|
||||
setBurnRateOption: (option: BurnRateOption) => void;
|
||||
burnRateOptions: BurnRateOption[];
|
||||
selectedTabId: SloTabId;
|
||||
}
|
||||
export function BurnRateHeader({
|
||||
burnRateOption,
|
||||
burnRateOptions,
|
||||
setBurnRateOption,
|
||||
selectedTabId,
|
||||
}: Props) {
|
||||
const onBurnRateOptionChange = (optionId: string) => {
|
||||
const selected = burnRateOptions.find((opt) => opt.id === optionId) ?? burnRateOptions[0];
|
||||
setBurnRateOption(selected);
|
||||
};
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.slo.burnRate.title', {
|
||||
defaultMessage: 'Burn rate',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('xpack.slo.burnRate.timeRangeBtnLegend', {
|
||||
defaultMessage: 'Select the time range',
|
||||
})}
|
||||
options={burnRateOptions.map((opt) => ({ id: opt.id, label: opt.label }))}
|
||||
idSelected={burnRateOption.id}
|
||||
onChange={onBurnRateOptionChange}
|
||||
buttonSize="compressed"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -5,11 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { TimeBounds } from '../../../pages/slo_details/types';
|
||||
import { TimeRange } from '../error_rate_chart/use_lens_definition';
|
||||
import { SloTabId } from '../../../pages/slo_details/components/slo_details';
|
||||
import { BurnRateHeader } from './burn_rate_header';
|
||||
import { useFetchSloBurnRates } from '../../../hooks/use_fetch_slo_burn_rates';
|
||||
import { ErrorRateChart } from '../error_rate_chart';
|
||||
import { BurnRate } from './burn_rate';
|
||||
|
@ -18,6 +21,9 @@ interface Props {
|
|||
slo: SLOWithSummaryResponse;
|
||||
isAutoRefreshing?: boolean;
|
||||
burnRateOptions: BurnRateOption[];
|
||||
selectedTabId: SloTabId;
|
||||
range?: TimeRange;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export interface BurnRateOption {
|
||||
|
@ -32,7 +38,14 @@ function getWindowsFromOptions(opts: BurnRateOption[]): Array<{ name: string; du
|
|||
return opts.map((opt) => ({ name: opt.windowName, duration: `${opt.duration}h` }));
|
||||
}
|
||||
|
||||
export function BurnRates({ slo, isAutoRefreshing, burnRateOptions }: Props) {
|
||||
export function BurnRates({
|
||||
slo,
|
||||
isAutoRefreshing,
|
||||
burnRateOptions,
|
||||
selectedTabId,
|
||||
range,
|
||||
onBrushed,
|
||||
}: Props) {
|
||||
const [burnRateOption, setBurnRateOption] = useState(burnRateOptions[0]);
|
||||
const { isLoading, data } = useFetchSloBurnRates({
|
||||
slo,
|
||||
|
@ -46,12 +59,7 @@ export function BurnRates({ slo, isAutoRefreshing, burnRateOptions }: Props) {
|
|||
}
|
||||
}, [burnRateOptions]);
|
||||
|
||||
const onBurnRateOptionChange = (optionId: string) => {
|
||||
const selected = burnRateOptions.find((opt) => opt.id === optionId) ?? burnRateOptions[0];
|
||||
setBurnRateOption(selected);
|
||||
};
|
||||
|
||||
const dataTimeRange = {
|
||||
const dataTimeRange = range ?? {
|
||||
from: moment().subtract(burnRateOption.duration, 'hour').toDate(),
|
||||
to: new Date(),
|
||||
};
|
||||
|
@ -64,34 +72,26 @@ export function BurnRates({ slo, isAutoRefreshing, burnRateOptions }: Props) {
|
|||
return (
|
||||
<EuiPanel paddingSize="m" color="transparent" hasBorder data-test-subj="burnRatePanel">
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.slo.burnRate.title', {
|
||||
defaultMessage: 'Burn rate',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('xpack.slo.burnRate.timeRangeBtnLegend', {
|
||||
defaultMessage: 'Select the time range',
|
||||
})}
|
||||
options={burnRateOptions.map((opt) => ({ id: opt.id, label: opt.label }))}
|
||||
idSelected={burnRateOption.id}
|
||||
onChange={onBurnRateOptionChange}
|
||||
buttonSize="compressed"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<BurnRateHeader
|
||||
burnRateOption={burnRateOption}
|
||||
burnRateOptions={burnRateOptions}
|
||||
setBurnRateOption={setBurnRateOption}
|
||||
selectedTabId={selectedTabId}
|
||||
/>
|
||||
<EuiFlexGroup direction="row" gutterSize="m">
|
||||
<EuiFlexItem grow={1}>
|
||||
<BurnRate threshold={threshold} burnRate={burnRate} slo={slo} isLoading={isLoading} />
|
||||
</EuiFlexItem>
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexItem grow={1}>
|
||||
<BurnRate threshold={threshold} burnRate={burnRate} slo={slo} isLoading={isLoading} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={3}>
|
||||
<ErrorRateChart slo={slo} dataTimeRange={dataTimeRange} threshold={threshold} />
|
||||
<ErrorRateChart
|
||||
slo={slo}
|
||||
dataTimeRange={dataTimeRange}
|
||||
threshold={threshold}
|
||||
onBrushed={onBrushed}
|
||||
selectedTabId={selectedTabId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -9,6 +9,8 @@ import { ViewMode } from '@kbn/embeddable-plugin/public';
|
|||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { SloTabId } from '../../../pages/slo_details/components/slo_details';
|
||||
import { TimeBounds } from '../../../pages/slo_details/types';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { getDelayInSecondsFromSLO } from '../../../utils/slo/get_delay_in_seconds_from_slo';
|
||||
import { AlertAnnotation, TimeRange, useLensDefinition } from './use_lens_definition';
|
||||
|
@ -20,6 +22,8 @@ interface Props {
|
|||
alertTimeRange?: TimeRange;
|
||||
showErrorRateAsLine?: boolean;
|
||||
annotations?: AlertAnnotation[];
|
||||
selectedTabId?: SloTabId;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function ErrorRateChart({
|
||||
|
@ -29,17 +33,20 @@ export function ErrorRateChart({
|
|||
alertTimeRange,
|
||||
showErrorRateAsLine,
|
||||
annotations,
|
||||
onBrushed,
|
||||
selectedTabId,
|
||||
}: Props) {
|
||||
const {
|
||||
lens: { EmbeddableComponent },
|
||||
} = useKibana().services;
|
||||
const lensDef = useLensDefinition(
|
||||
const lensDef = useLensDefinition({
|
||||
slo,
|
||||
threshold,
|
||||
alertTimeRange,
|
||||
annotations,
|
||||
showErrorRateAsLine
|
||||
);
|
||||
showErrorRateAsLine,
|
||||
selectedTabId,
|
||||
});
|
||||
const delayInSeconds = getDelayInSecondsFromSLO(slo);
|
||||
|
||||
const from = moment(dataTimeRange.from).subtract(delayInSeconds, 'seconds').toISOString();
|
||||
|
@ -55,6 +62,14 @@ export function ErrorRateChart({
|
|||
}}
|
||||
attributes={lensDef}
|
||||
viewMode={ViewMode.VIEW}
|
||||
onBrushEnd={({ range }) => {
|
||||
onBrushed?.({
|
||||
from: range[0],
|
||||
to: range[1],
|
||||
fromUtc: moment(range[0]).format(),
|
||||
toUtc: moment(range[1]).format(),
|
||||
});
|
||||
}}
|
||||
noPadding
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
|||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SloTabId } from '../../../pages/slo_details/components/slo_details';
|
||||
import { SLO_DESTINATION_INDEX_PATTERN } from '../../../../common/constants';
|
||||
|
||||
export interface TimeRange {
|
||||
|
@ -24,13 +25,21 @@ export interface AlertAnnotation {
|
|||
total: number;
|
||||
}
|
||||
|
||||
export function useLensDefinition(
|
||||
slo: SLOWithSummaryResponse,
|
||||
threshold: number,
|
||||
alertTimeRange?: TimeRange,
|
||||
annotations?: AlertAnnotation[],
|
||||
showErrorRateAsLine?: boolean
|
||||
): TypedLensByValueInput['attributes'] {
|
||||
export function useLensDefinition({
|
||||
slo,
|
||||
threshold,
|
||||
alertTimeRange,
|
||||
annotations,
|
||||
showErrorRateAsLine,
|
||||
selectedTabId,
|
||||
}: {
|
||||
slo: SLOWithSummaryResponse;
|
||||
threshold: number;
|
||||
alertTimeRange?: TimeRange;
|
||||
annotations?: AlertAnnotation[];
|
||||
showErrorRateAsLine?: boolean;
|
||||
selectedTabId?: SloTabId;
|
||||
}): TypedLensByValueInput['attributes'] {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const interval = 'auto';
|
||||
|
@ -87,20 +96,24 @@ export function useLensDefinition(
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
layerId: '34298f84-681e-4fa3-8107-d6facb32ed92',
|
||||
layerType: 'referenceLine',
|
||||
accessors: ['0a42b72b-cd5a-4d59-81ec-847d97c268e6'],
|
||||
yConfig: [
|
||||
{
|
||||
forAccessor: '0a42b72b-cd5a-4d59-81ec-847d97c268e6',
|
||||
axisMode: 'left',
|
||||
textVisibility: true,
|
||||
color: euiTheme.colors.danger,
|
||||
iconPosition: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
...(selectedTabId !== 'history'
|
||||
? [
|
||||
{
|
||||
layerId: '34298f84-681e-4fa3-8107-d6facb32ed92',
|
||||
layerType: 'referenceLine',
|
||||
accessors: ['0a42b72b-cd5a-4d59-81ec-847d97c268e6'],
|
||||
yConfig: [
|
||||
{
|
||||
forAccessor: '0a42b72b-cd5a-4d59-81ec-847d97c268e6',
|
||||
axisMode: 'left',
|
||||
textVisibility: true,
|
||||
color: euiTheme.colors.danger,
|
||||
iconPosition: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!!alertTimeRange
|
||||
? [
|
||||
{
|
||||
|
|
|
@ -79,9 +79,9 @@ export function SloOverviewDetails({
|
|||
{tabs.map((tab, index) => (
|
||||
<EuiTab
|
||||
key={index}
|
||||
onClick={tab.onClick}
|
||||
onClick={'onClick' in tab ? tab.onClick : undefined}
|
||||
isSelected={tab.id === selectedTabId}
|
||||
append={tab.append}
|
||||
append={'append' in tab ? tab.append : null}
|
||||
>
|
||||
{tab.label}
|
||||
</EuiTab>
|
||||
|
|
|
@ -23,11 +23,16 @@ export interface UseFetchHistoricalSummaryResponse {
|
|||
export interface Params {
|
||||
sloList: SLOWithSummaryResponse[];
|
||||
shouldRefetch?: boolean;
|
||||
range?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function useFetchHistoricalSummary({
|
||||
sloList = [],
|
||||
shouldRefetch,
|
||||
range,
|
||||
}: Params): UseFetchHistoricalSummaryResponse {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
|
@ -40,6 +45,7 @@ export function useFetchHistoricalSummary({
|
|||
revision: slo.revision,
|
||||
objective: slo.objective,
|
||||
budgetingMethod: slo.budgetingMethod,
|
||||
range,
|
||||
}));
|
||||
|
||||
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
|
||||
|
|
|
@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { TimeBounds } from '../types';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { toDuration, toMinutes } from '../../../utils/slo/duration';
|
||||
import { ChartData } from '../../../typings/slo';
|
||||
|
@ -33,9 +35,11 @@ export interface Props {
|
|||
data: ChartData[];
|
||||
isLoading: boolean;
|
||||
slo: SLOWithSummaryResponse;
|
||||
selectedTabId?: SloTabId;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function ErrorBudgetChart({ data, isLoading, slo }: Props) {
|
||||
export function ErrorBudgetChart({ data, isLoading, slo, selectedTabId, onBrushed }: Props) {
|
||||
const { uiSettings } = useKibana().services;
|
||||
const percentFormat = uiSettings.get('format:percent:defaultPattern');
|
||||
const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED';
|
||||
|
@ -53,23 +57,12 @@ export function ErrorBudgetChart({ data, isLoading, slo }: Props) {
|
|||
}
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="row" gutterSize="l" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
titleColor={isSloFailed ? 'danger' : 'success'}
|
||||
title={numeral(slo.summary.errorBudget.remaining).format(percentFormat)}
|
||||
titleSize="s"
|
||||
description={i18n.translate('xpack.slo.sloDetails.errorBudgetChartPanel.remaining', {
|
||||
defaultMessage: 'Remaining',
|
||||
})}
|
||||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{errorBudgetTimeRemainingFormatted ? (
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexGroup direction="row" gutterSize="l" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
titleColor={isSloFailed ? 'danger' : 'success'}
|
||||
title={errorBudgetTimeRemainingFormatted}
|
||||
title={numeral(slo.summary.errorBudget.remaining).format(percentFormat)}
|
||||
titleSize="s"
|
||||
description={i18n.translate('xpack.slo.sloDetails.errorBudgetChartPanel.remaining', {
|
||||
defaultMessage: 'Remaining',
|
||||
|
@ -77,8 +70,24 @@ export function ErrorBudgetChart({ data, isLoading, slo }: Props) {
|
|||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
{errorBudgetTimeRemainingFormatted ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
titleColor={isSloFailed ? 'danger' : 'success'}
|
||||
title={errorBudgetTimeRemainingFormatted}
|
||||
titleSize="s"
|
||||
description={i18n.translate(
|
||||
'xpack.slo.sloDetails.errorBudgetChartPanel.remaining',
|
||||
{
|
||||
defaultMessage: 'Remaining',
|
||||
}
|
||||
)}
|
||||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<EuiFlexItem>
|
||||
<WideChart
|
||||
|
@ -89,6 +98,7 @@ export function ErrorBudgetChart({ data, isLoading, slo }: Props) {
|
|||
state={isSloFailed ? 'error' : 'success'}
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
|
|
|
@ -14,6 +14,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { SaveModalDashboardProps } from '@kbn/presentation-util-plugin/public';
|
||||
import { TimeBounds } from '../types';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { ChartData } from '../../../typings/slo';
|
||||
import { ErrorBudgetChart } from './error_budget_chart';
|
||||
|
@ -24,9 +26,11 @@ export interface Props {
|
|||
data: ChartData[];
|
||||
isLoading: boolean;
|
||||
slo: SLOWithSummaryResponse;
|
||||
selectedTabId: SloTabId;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) {
|
||||
export function ErrorBudgetChartPanel({ data, isLoading, slo, selectedTabId, onBrushed }: Props) {
|
||||
const [isMouseOver, setIsMouseOver] = useState(false);
|
||||
|
||||
const [isDashboardAttachmentReady, setDashboardAttachmentReady] = useState(false);
|
||||
|
@ -81,9 +85,16 @@ export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) {
|
|||
showTitle={true}
|
||||
isMouseOver={isMouseOver}
|
||||
setDashboardAttachmentReady={setDashboardAttachmentReady}
|
||||
selectedTabId={selectedTabId}
|
||||
/>
|
||||
|
||||
<ErrorBudgetChart slo={slo} data={data} isLoading={isLoading} />
|
||||
<ErrorBudgetChart
|
||||
slo={slo}
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
selectedTabId={selectedTabId}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
{isDashboardAttachmentReady ? (
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { toDurationAdverbLabel, toDurationLabel } from '../../../utils/slo/labels';
|
||||
|
||||
|
@ -19,6 +20,7 @@ interface Props {
|
|||
showTitle?: boolean;
|
||||
isMouseOver?: boolean;
|
||||
setDashboardAttachmentReady?: (value: boolean) => void;
|
||||
selectedTabId?: SloTabId;
|
||||
}
|
||||
|
||||
export function ErrorBudgetHeader({
|
||||
|
@ -26,6 +28,7 @@ export function ErrorBudgetHeader({
|
|||
showTitle = true,
|
||||
isMouseOver,
|
||||
setDashboardAttachmentReady,
|
||||
selectedTabId,
|
||||
}: Props) {
|
||||
const { executionContext } = useKibana().services;
|
||||
const executionContextName = executionContext.get().name;
|
||||
|
@ -57,16 +60,18 @@ export function ErrorBudgetHeader({
|
|||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{rollingTimeWindowTypeSchema.is(slo.timeWindow.type)
|
||||
? i18n.translate('xpack.slo.sloDetails.errorBudgetChartPanel.duration', {
|
||||
defaultMessage: 'Last {duration}',
|
||||
values: { duration: toDurationLabel(slo.timeWindow.duration) },
|
||||
})
|
||||
: toDurationAdverbLabel(slo.timeWindow.duration)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{rollingTimeWindowTypeSchema.is(slo.timeWindow.type)
|
||||
? i18n.translate('xpack.slo.sloDetails.errorBudgetChartPanel.duration', {
|
||||
defaultMessage: 'Last {duration}',
|
||||
values: { duration: toDurationLabel(slo.timeWindow.duration) },
|
||||
})
|
||||
: toDurationAdverbLabel(slo.timeWindow.duration)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ import { max, min } from 'lodash';
|
|||
import moment from 'moment';
|
||||
import React, { useRef } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { TimeBounds } from '../types';
|
||||
import { getBrushData } from '../../../utils/slo/duration';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { useGetPreviewData } from '../../../hooks/use_get_preview_data';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { COMPARATOR_MAPPING } from '../../slo_edit/constants';
|
||||
|
@ -48,9 +51,11 @@ export interface Props {
|
|||
start: number;
|
||||
end: number;
|
||||
};
|
||||
selectedTabId: SloTabId;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function EventsChartPanel({ slo, range }: Props) {
|
||||
export function EventsChartPanel({ slo, range, selectedTabId, onBrushed }: Props) {
|
||||
const { charts, uiSettings, discover } = useKibana().services;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const baseTheme = charts.theme.useChartsBaseTheme();
|
||||
|
@ -157,13 +162,15 @@ export function EventsChartPanel({ slo, range }: Props) {
|
|||
<EuiFlexGroup>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={1}> {title}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{i18n.translate('xpack.slo.sloDetails.eventsChartPanel.duration', {
|
||||
defaultMessage: 'Last 24h',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{i18n.translate('xpack.slo.sloDetails.eventsChartPanel.duration', {
|
||||
defaultMessage: 'Last 24h',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{showViewEventsLink && (
|
||||
<EuiFlexItem grow={0}>
|
||||
|
@ -193,6 +200,7 @@ export function EventsChartPanel({ slo, range }: Props) {
|
|||
data={data || []}
|
||||
annotation={annotation}
|
||||
slo={slo}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
@ -225,6 +233,9 @@ export function EventsChartPanel({ slo, range }: Props) {
|
|||
pointerUpdateDebounce={0}
|
||||
pointerUpdateTrigger={'x'}
|
||||
locale={i18n.getLocale()}
|
||||
onBrushEnd={(brushArea) => {
|
||||
onBrushed?.(getBrushData(brushArea));
|
||||
}}
|
||||
/>
|
||||
{annotation}
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { TimeBounds } from '../types';
|
||||
import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary';
|
||||
import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { SliChartPanel } from './sli_chart_panel';
|
||||
import { ErrorBudgetChartPanel } from './error_budget_chart_panel';
|
||||
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse;
|
||||
isAutoRefreshing: boolean;
|
||||
selectedTabId: SloTabId;
|
||||
range?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
export function HistoricalDataCharts({
|
||||
slo,
|
||||
range,
|
||||
isAutoRefreshing,
|
||||
selectedTabId,
|
||||
onBrushed,
|
||||
}: Props) {
|
||||
const { data: historicalSummaries = [], isLoading: historicalSummaryLoading } =
|
||||
useFetchHistoricalSummary({
|
||||
sloList: [slo],
|
||||
shouldRefetch: isAutoRefreshing,
|
||||
range,
|
||||
});
|
||||
|
||||
const sloHistoricalSummary = historicalSummaries.find(
|
||||
(historicalSummary) =>
|
||||
historicalSummary.sloId === slo.id &&
|
||||
historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE)
|
||||
);
|
||||
|
||||
const errorBudgetBurnDownData = formatHistoricalData(
|
||||
sloHistoricalSummary?.data,
|
||||
'error_budget_remaining'
|
||||
);
|
||||
const historicalSliData = formatHistoricalData(sloHistoricalSummary?.data, 'sli_value');
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<SliChartPanel
|
||||
data={historicalSliData}
|
||||
isLoading={historicalSummaryLoading}
|
||||
slo={slo}
|
||||
selectedTabId={selectedTabId}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ErrorBudgetChartPanel
|
||||
data={errorBudgetBurnDownData}
|
||||
isLoading={historicalSummaryLoading}
|
||||
slo={slo}
|
||||
selectedTabId={selectedTabId}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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 React, { useCallback, useMemo, useState } from 'react';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSuperDatePicker,
|
||||
OnTimeChangeProps,
|
||||
OnRefreshProps,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import DateMath from '@kbn/datemath';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { HistoricalDataCharts } from '../historical_data_charts';
|
||||
import { useBurnRateOptions } from '../../hooks/use_burn_rate_options';
|
||||
import { SloTabId } from '../slo_details';
|
||||
import { BurnRates } from '../../../../components/slo/burn_rate/burn_rates';
|
||||
import { EventsChartPanel } from '../events_chart_panel';
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse;
|
||||
isAutoRefreshing: boolean;
|
||||
selectedTabId: SloTabId;
|
||||
}
|
||||
|
||||
export function SLODetailsHistory({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
||||
const { uiSettings } = useKibana().services;
|
||||
|
||||
const { burnRateOptions } = useBurnRateOptions(slo);
|
||||
|
||||
const [start, setStart] = useState('now-30d');
|
||||
const [end, setEnd] = useState('now');
|
||||
|
||||
const onTimeChange = (val: OnTimeChangeProps) => {
|
||||
setStart(val.start);
|
||||
setEnd(val.end);
|
||||
};
|
||||
|
||||
const onRefresh = (val: OnRefreshProps) => {};
|
||||
|
||||
const absRange = useMemo(() => {
|
||||
return {
|
||||
from: new Date(DateMath.parse(start)!.valueOf()),
|
||||
to: new Date(DateMath.parse(end, { roundUp: true })!.valueOf()),
|
||||
absoluteFrom: DateMath.parse(start)!.valueOf(),
|
||||
absoluteTo: DateMath.parse(end, { roundUp: true })!.valueOf(),
|
||||
};
|
||||
}, [start, end]);
|
||||
|
||||
const onBrushed = useCallback(({ fromUtc, toUtc }) => {
|
||||
setStart(fromUtc);
|
||||
setEnd(toUtc);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem
|
||||
grow
|
||||
css={{
|
||||
maxWidth: 500,
|
||||
}}
|
||||
>
|
||||
<EuiSuperDatePicker
|
||||
isLoading={false}
|
||||
start={start}
|
||||
end={end}
|
||||
onTimeChange={onTimeChange}
|
||||
onRefresh={onRefresh}
|
||||
width="full"
|
||||
commonlyUsedRanges={uiSettings
|
||||
.get('timepicker:quickRanges')
|
||||
.map(({ from, to, display }: { from: string; to: string; display: string }) => {
|
||||
return {
|
||||
start: from,
|
||||
end: to,
|
||||
label: display,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup direction="column" gutterSize="l">
|
||||
<EuiFlexItem>
|
||||
<BurnRates
|
||||
slo={slo}
|
||||
isAutoRefreshing={isAutoRefreshing}
|
||||
burnRateOptions={burnRateOptions}
|
||||
selectedTabId={selectedTabId}
|
||||
range={{
|
||||
from: absRange.from,
|
||||
to: absRange.to,
|
||||
}}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<HistoricalDataCharts
|
||||
slo={slo}
|
||||
selectedTabId={selectedTabId}
|
||||
isAutoRefreshing={isAutoRefreshing}
|
||||
range={{
|
||||
from: start,
|
||||
to: end,
|
||||
}}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
<EuiFlexItem>
|
||||
<EventsChartPanel
|
||||
slo={slo}
|
||||
range={{
|
||||
start: absRange.absoluteFrom,
|
||||
end: absRange.absoluteTo,
|
||||
}}
|
||||
selectedTabId={selectedTabId}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -10,6 +10,8 @@ import numeral from '@elastic/numeral';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
import { TimeBounds } from '../types';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { ChartData } from '../../../typings/slo';
|
||||
import { toDurationAdverbLabel, toDurationLabel } from '../../../utils/slo/labels';
|
||||
|
@ -19,9 +21,11 @@ export interface Props {
|
|||
data: ChartData[];
|
||||
isLoading: boolean;
|
||||
slo: SLOWithSummaryResponse;
|
||||
selectedTabId: SloTabId;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function SliChartPanel({ data, isLoading, slo }: Props) {
|
||||
export function SliChartPanel({ data, isLoading, slo, selectedTabId, onBrushed }: Props) {
|
||||
const { uiSettings } = useKibana().services;
|
||||
const percentFormat = uiSettings.get('format:percent:defaultPattern');
|
||||
|
||||
|
@ -41,41 +45,45 @@ export function SliChartPanel({ data, isLoading, slo }: Props) {
|
|||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{rollingTimeWindowTypeSchema.is(slo.timeWindow.type)
|
||||
? i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.duration', {
|
||||
defaultMessage: 'Last {duration}',
|
||||
values: { duration: toDurationLabel(slo.timeWindow.duration) },
|
||||
})
|
||||
: toDurationAdverbLabel(slo.timeWindow.duration)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{rollingTimeWindowTypeSchema.is(slo.timeWindow.type)
|
||||
? i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.duration', {
|
||||
defaultMessage: 'Last {duration}',
|
||||
values: { duration: toDurationLabel(slo.timeWindow.duration) },
|
||||
})
|
||||
: toDurationAdverbLabel(slo.timeWindow.duration)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup direction="row" gutterSize="l" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
titleColor={isSloFailed ? 'danger' : 'success'}
|
||||
title={hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat)}
|
||||
titleSize="s"
|
||||
description={i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.current', {
|
||||
defaultMessage: 'Observed value',
|
||||
})}
|
||||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
title={numeral(slo.objective.target).format(percentFormat)}
|
||||
titleSize="s"
|
||||
description={i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.objective', {
|
||||
defaultMessage: 'Objective',
|
||||
})}
|
||||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{selectedTabId !== 'history' && (
|
||||
<EuiFlexGroup direction="row" gutterSize="l" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
titleColor={isSloFailed ? 'danger' : 'success'}
|
||||
title={hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat)}
|
||||
titleSize="s"
|
||||
description={i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.current', {
|
||||
defaultMessage: 'Observed value',
|
||||
})}
|
||||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStat
|
||||
title={numeral(slo.objective.target).format(percentFormat)}
|
||||
titleSize="s"
|
||||
description={i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.objective', {
|
||||
defaultMessage: 'Objective',
|
||||
})}
|
||||
reverse
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<EuiFlexItem>
|
||||
<WideChart
|
||||
|
@ -86,6 +94,7 @@ export function SliChartPanel({ data, isLoading, slo }: Props) {
|
|||
state={isSloFailed ? 'error' : 'success'}
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -5,71 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { 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';
|
||||
import { ErrorBudgetChartPanel } from './error_budget_chart_panel';
|
||||
import { HistoricalDataCharts } from './historical_data_charts';
|
||||
import { useBurnRateOptions } from '../hooks/use_burn_rate_options';
|
||||
import { SLODetailsHistory } from './history/slo_details_history';
|
||||
import { BurnRates } from '../../../components/slo/burn_rate/burn_rates';
|
||||
import { EventsChartPanel } from './events_chart_panel';
|
||||
import { Overview } from './overview/overview';
|
||||
import { SliChartPanel } from './sli_chart_panel';
|
||||
import { SloDetailsAlerts } from './slo_detail_alerts';
|
||||
import { SloHealthCallout } from './slo_health_callout';
|
||||
import { SloRemoteCallout } from './slo_remote_callout';
|
||||
|
||||
export const TAB_ID_URL_PARAM = 'tabId';
|
||||
export const OVERVIEW_TAB_ID = 'overview';
|
||||
export const HISTORY_TAB_ID = 'history';
|
||||
export const ALERTS_TAB_ID = 'alerts';
|
||||
const DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
|
||||
|
||||
const DEFAULT_BURN_RATE_OPTIONS: BurnRateOption[] = [
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 1 },
|
||||
}),
|
||||
windowName: 'CRITICAL',
|
||||
threshold: 14.4,
|
||||
duration: 1,
|
||||
},
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 6 },
|
||||
}),
|
||||
windowName: 'HIGH',
|
||||
threshold: 6,
|
||||
duration: 6,
|
||||
},
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 24 },
|
||||
}),
|
||||
windowName: 'MEDIUM',
|
||||
threshold: 3,
|
||||
duration: 24,
|
||||
},
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 72 },
|
||||
}),
|
||||
windowName: 'LOW',
|
||||
threshold: 1,
|
||||
duration: 72,
|
||||
},
|
||||
];
|
||||
|
||||
export type SloTabId = typeof OVERVIEW_TAB_ID | typeof ALERTS_TAB_ID;
|
||||
export type SloTabId = typeof OVERVIEW_TAB_ID | typeof ALERTS_TAB_ID | typeof HISTORY_TAB_ID;
|
||||
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse;
|
||||
|
@ -77,30 +32,7 @@ export interface Props {
|
|||
selectedTabId: SloTabId;
|
||||
}
|
||||
export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
||||
const { data: rules } = useFetchRulesForSlo({ sloIds: [slo.id] });
|
||||
const burnRateOptions =
|
||||
rules?.[slo.id]?.[0]?.params?.windows?.map((window) => ({
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: window.longWindow.value },
|
||||
}),
|
||||
windowName: window.actionGroup,
|
||||
threshold: window.burnRateThreshold,
|
||||
duration: window.longWindow.value,
|
||||
})) ?? DEFAULT_BURN_RATE_OPTIONS;
|
||||
|
||||
const { data: historicalSummaries = [], isLoading: historicalSummaryLoading } =
|
||||
useFetchHistoricalSummary({
|
||||
sloList: [slo],
|
||||
shouldRefetch: isAutoRefreshing,
|
||||
});
|
||||
|
||||
const sloHistoricalSummary = historicalSummaries.find(
|
||||
(historicalSummary) =>
|
||||
historicalSummary.sloId === slo.id &&
|
||||
historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE)
|
||||
);
|
||||
const { burnRateOptions } = useBurnRateOptions(slo);
|
||||
|
||||
const [range, setRange] = useState({
|
||||
start: new Date().getTime() - DAY_IN_MILLISECONDS,
|
||||
|
@ -118,12 +50,6 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
|||
return () => clearInterval(intervalId);
|
||||
}, [isAutoRefreshing]);
|
||||
|
||||
const errorBudgetBurnDownData = formatHistoricalData(
|
||||
sloHistoricalSummary?.data,
|
||||
'error_budget_remaining'
|
||||
);
|
||||
const historicalSliData = formatHistoricalData(sloHistoricalSummary?.data, 'sli_value');
|
||||
|
||||
return selectedTabId === OVERVIEW_TAB_ID ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="xl">
|
||||
<SloRemoteCallout slo={slo} />
|
||||
|
@ -137,24 +63,26 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
|||
slo={slo}
|
||||
isAutoRefreshing={isAutoRefreshing}
|
||||
burnRateOptions={burnRateOptions}
|
||||
selectedTabId={selectedTabId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<HistoricalDataCharts
|
||||
slo={slo}
|
||||
selectedTabId={selectedTabId}
|
||||
isAutoRefreshing={isAutoRefreshing}
|
||||
/>
|
||||
<EuiFlexItem>
|
||||
<SliChartPanel data={historicalSliData} isLoading={historicalSummaryLoading} slo={slo} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ErrorBudgetChartPanel
|
||||
data={errorBudgetBurnDownData}
|
||||
isLoading={historicalSummaryLoading}
|
||||
slo={slo}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EventsChartPanel slo={slo} range={range} />
|
||||
<EventsChartPanel slo={slo} range={range} selectedTabId={selectedTabId} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
) : selectedTabId === ALERTS_TAB_ID ? (
|
||||
<SloDetailsAlerts slo={slo} />
|
||||
) : (
|
||||
<SLODetailsHistory
|
||||
slo={slo}
|
||||
isAutoRefreshing={isAutoRefreshing}
|
||||
selectedTabId={selectedTabId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import moment from 'moment';
|
|||
import React, { useRef } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getBrushData } from '../../../utils/slo/duration';
|
||||
import { TimeBounds } from '../types';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { ChartData } from '../../../typings';
|
||||
|
||||
|
@ -36,9 +38,10 @@ export interface Props {
|
|||
chart: ChartType;
|
||||
state: State;
|
||||
isLoading: boolean;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function WideChart({ chart, data, id, isLoading, state }: Props) {
|
||||
export function WideChart({ chart, data, id, isLoading, state, onBrushed }: Props) {
|
||||
const { charts, uiSettings } = useKibana().services;
|
||||
const baseTheme = charts.theme.useChartsBaseTheme();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -63,7 +66,16 @@ export function WideChart({ chart, data, id, isLoading, state }: Props) {
|
|||
<Settings
|
||||
baseTheme={baseTheme}
|
||||
showLegend={false}
|
||||
noResults={<EuiIcon type="visualizeApp" size="l" color="subdued" title="no results" />}
|
||||
noResults={
|
||||
<EuiIcon
|
||||
type="visualizeApp"
|
||||
size="l"
|
||||
color="subdued"
|
||||
title={i18n.translate('xpack.slo.wideChart.euiIcon.noResultsLabel', {
|
||||
defaultMessage: 'no results',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
externalPointerEvents={{
|
||||
tooltip: { visible: true },
|
||||
|
@ -71,6 +83,9 @@ export function WideChart({ chart, data, id, isLoading, state }: Props) {
|
|||
pointerUpdateDebounce={0}
|
||||
pointerUpdateTrigger={'x'}
|
||||
locale={i18n.getLocale()}
|
||||
onBrushEnd={(brushArea) => {
|
||||
onBrushed?.(getBrushData(brushArea));
|
||||
}}
|
||||
/>
|
||||
<Axis
|
||||
id="bottom"
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { htmlIdGenerator } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { BurnRateOption } from '../../../components/slo/burn_rate/burn_rates';
|
||||
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
|
||||
|
||||
export const DEFAULT_BURN_RATE_OPTIONS: BurnRateOption[] = [
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 1 },
|
||||
}),
|
||||
windowName: 'CRITICAL',
|
||||
threshold: 14.4,
|
||||
duration: 1,
|
||||
},
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 6 },
|
||||
}),
|
||||
windowName: 'HIGH',
|
||||
threshold: 6,
|
||||
duration: 6,
|
||||
},
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 24 },
|
||||
}),
|
||||
windowName: 'MEDIUM',
|
||||
threshold: 3,
|
||||
duration: 24,
|
||||
},
|
||||
{
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: 72 },
|
||||
}),
|
||||
windowName: 'LOW',
|
||||
threshold: 1,
|
||||
duration: 72,
|
||||
},
|
||||
];
|
||||
|
||||
export const useBurnRateOptions = (slo: SLOWithSummaryResponse) => {
|
||||
const { data: rules } = useFetchRulesForSlo({ sloIds: [slo.id] });
|
||||
const burnRateOptions =
|
||||
rules?.[slo.id]?.[0]?.params?.windows?.map((window) => ({
|
||||
id: htmlIdGenerator()(),
|
||||
label: i18n.translate('xpack.slo.burnRates.fromRange.label', {
|
||||
defaultMessage: '{duration}h',
|
||||
values: { duration: window.longWindow.value },
|
||||
}),
|
||||
windowName: window.actionGroup,
|
||||
threshold: window.burnRateThreshold,
|
||||
duration: window.longWindow.value,
|
||||
})) ?? DEFAULT_BURN_RATE_OPTIONS;
|
||||
|
||||
return { burnRateOptions };
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { SloDetailsPathParams } from '../types';
|
||||
import {
|
||||
ALERTS_TAB_ID,
|
||||
HISTORY_TAB_ID,
|
||||
OVERVIEW_TAB_ID,
|
||||
SloTabId,
|
||||
} from '../components/slo_details';
|
||||
|
||||
export const useSelectedTab = () => {
|
||||
const { tabId } = useParams<SloDetailsPathParams>();
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(() => {
|
||||
return tabId && [OVERVIEW_TAB_ID, ALERTS_TAB_ID, HISTORY_TAB_ID].includes(tabId)
|
||||
? (tabId as SloTabId)
|
||||
: OVERVIEW_TAB_ID;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// update the url when the selected tab changes
|
||||
if (tabId !== selectedTabId) {
|
||||
setSelectedTabId(tabId as SloTabId);
|
||||
}
|
||||
}, [selectedTabId, tabId]);
|
||||
|
||||
return {
|
||||
selectedTabId: selectedTabId || OVERVIEW_TAB_ID,
|
||||
setSelectedTabId,
|
||||
};
|
||||
};
|
|
@ -9,8 +9,15 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiNotificationBadge, EuiToolTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { useFetchActiveAlerts } from '../../../hooks/use_fetch_active_alerts';
|
||||
import { ALERTS_TAB_ID, OVERVIEW_TAB_ID, SloTabId } from '../components/slo_details';
|
||||
import {
|
||||
ALERTS_TAB_ID,
|
||||
HISTORY_TAB_ID,
|
||||
OVERVIEW_TAB_ID,
|
||||
SloTabId,
|
||||
} from '../components/slo_details';
|
||||
|
||||
export const useSloDetailsTabs = ({
|
||||
slo,
|
||||
|
@ -21,13 +28,15 @@ export const useSloDetailsTabs = ({
|
|||
slo?: SLOWithSummaryResponse | null;
|
||||
isAutoRefreshing: boolean;
|
||||
selectedTabId: SloTabId;
|
||||
setSelectedTabId: (val: SloTabId) => void;
|
||||
setSelectedTabId?: (val: SloTabId) => void;
|
||||
}) => {
|
||||
const { data: activeAlerts } = useFetchActiveAlerts({
|
||||
sloIdsAndInstanceIds: slo ? [[slo.id, slo.instanceId ?? ALL_VALUE]] : [],
|
||||
shouldRefetch: isAutoRefreshing,
|
||||
});
|
||||
|
||||
const { basePath } = useKibana().services.http;
|
||||
|
||||
const isRemote = !!slo?.remote;
|
||||
|
||||
const tabs = [
|
||||
|
@ -38,8 +47,47 @@ export const useSloDetailsTabs = ({
|
|||
}),
|
||||
'data-test-subj': 'overviewTab',
|
||||
isSelected: selectedTabId === OVERVIEW_TAB_ID,
|
||||
onClick: () => setSelectedTabId(OVERVIEW_TAB_ID),
|
||||
...(setSelectedTabId
|
||||
? {
|
||||
onClick: () => setSelectedTabId(OVERVIEW_TAB_ID),
|
||||
}
|
||||
: {
|
||||
href: slo
|
||||
? `${basePath.get()}${paths.sloDetails(
|
||||
slo.id,
|
||||
slo.instanceId,
|
||||
slo.remote?.remoteName,
|
||||
OVERVIEW_TAB_ID
|
||||
)}`
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
...(slo?.timeWindow.type === 'rolling'
|
||||
? [
|
||||
{
|
||||
id: HISTORY_TAB_ID,
|
||||
label: i18n.translate('xpack.slo.sloDetails.tab.historyLabel', {
|
||||
defaultMessage: 'History',
|
||||
}),
|
||||
'data-test-subj': 'historyTab',
|
||||
isSelected: selectedTabId === HISTORY_TAB_ID,
|
||||
...(setSelectedTabId
|
||||
? {
|
||||
onClick: () => setSelectedTabId(HISTORY_TAB_ID),
|
||||
}
|
||||
: {
|
||||
href: slo
|
||||
? `${basePath.get()}${paths.sloDetails(
|
||||
slo.id,
|
||||
slo.instanceId,
|
||||
slo.remote?.remoteName,
|
||||
HISTORY_TAB_ID
|
||||
)}`
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: ALERTS_TAB_ID,
|
||||
label: isRemote ? (
|
||||
|
@ -63,7 +111,20 @@ export const useSloDetailsTabs = ({
|
|||
{(activeAlerts && activeAlerts.get(slo)) ?? 0}
|
||||
</EuiNotificationBadge>
|
||||
) : null,
|
||||
onClick: () => setSelectedTabId(ALERTS_TAB_ID),
|
||||
...(setSelectedTabId
|
||||
? {
|
||||
onClick: () => setSelectedTabId(ALERTS_TAB_ID),
|
||||
}
|
||||
: {
|
||||
href: slo
|
||||
? `${basePath.get()}${paths.sloDetails(
|
||||
slo.id,
|
||||
slo.instanceId,
|
||||
slo.remote?.remoteName,
|
||||
ALERTS_TAB_ID
|
||||
)}`
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ const mockKibana = () => {
|
|||
http: {
|
||||
basePath: {
|
||||
prepend: (url: string) => url,
|
||||
get: () => 'http://localhost:5601',
|
||||
},
|
||||
},
|
||||
dataViews: {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useIsMutating } from '@tanstack/react-query';
|
||||
import { EuiLoadingSpinner, EuiSkeletonText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -16,6 +16,7 @@ import type { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
|||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
import dedent from 'dedent';
|
||||
import { useSelectedTab } from './hooks/use_selected_tab';
|
||||
import { HeaderMenu } from '../../components/header_menu/header_menu';
|
||||
import { useSloDetailsTabs } from './hooks/use_slo_details_tabs';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
|
@ -23,13 +24,7 @@ import { usePluginContext } from '../../hooks/use_plugin_context';
|
|||
import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import PageNotFound from '../404';
|
||||
import {
|
||||
ALERTS_TAB_ID,
|
||||
OVERVIEW_TAB_ID,
|
||||
SloDetails,
|
||||
TAB_ID_URL_PARAM,
|
||||
SloTabId,
|
||||
} from './components/slo_details';
|
||||
import { SloDetails } from './components/slo_details';
|
||||
import { HeaderTitle } from './components/header_title';
|
||||
import { HeaderControl } from './components/header_control';
|
||||
import { paths } from '../../../common/locators/paths';
|
||||
|
@ -45,7 +40,6 @@ export function SloDetailsPage() {
|
|||
observabilityAIAssistant,
|
||||
} = useKibana().services;
|
||||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
const { search } = useLocation();
|
||||
const { hasAtLeast } = useLicense();
|
||||
const hasRightLicense = hasAtLeast('platinum');
|
||||
|
||||
|
@ -61,19 +55,12 @@ export function SloDetailsPage() {
|
|||
});
|
||||
const isDeleting = Boolean(useIsMutating(['deleteSlo']));
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(() => {
|
||||
const searchParams = new URLSearchParams(search);
|
||||
const urlTabId = searchParams.get(TAB_ID_URL_PARAM);
|
||||
return urlTabId && [OVERVIEW_TAB_ID, ALERTS_TAB_ID].includes(urlTabId)
|
||||
? (urlTabId as SloTabId)
|
||||
: OVERVIEW_TAB_ID;
|
||||
});
|
||||
const { selectedTabId } = useSelectedTab();
|
||||
|
||||
const { tabs } = useSloDetailsTabs({
|
||||
slo,
|
||||
isAutoRefreshing,
|
||||
selectedTabId,
|
||||
setSelectedTabId,
|
||||
});
|
||||
|
||||
useBreadcrumbs(getBreadcrumbs(basePath, slo));
|
||||
|
|
|
@ -7,4 +7,12 @@
|
|||
|
||||
export interface SloDetailsPathParams {
|
||||
sloId: string;
|
||||
tabId?: string;
|
||||
}
|
||||
|
||||
export interface TimeBounds {
|
||||
from: number;
|
||||
to: number;
|
||||
fromUtc: string;
|
||||
toUtc: string;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { GetPreviewDataResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React, { useRef } from 'react';
|
||||
import { TimeBounds } from '../../../slo_details/types';
|
||||
import { getBrushData } from '../../../../utils/slo/duration';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { openInDiscover } from '../../../../utils/slo/get_discover_link';
|
||||
|
||||
|
@ -32,6 +34,7 @@ export interface Props {
|
|||
annotation?: React.ReactNode;
|
||||
isLoading?: boolean;
|
||||
bottomTitle?: string;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function GoodBadEventsChart({
|
||||
|
@ -39,6 +42,7 @@ export function GoodBadEventsChart({
|
|||
bottomTitle,
|
||||
data,
|
||||
slo,
|
||||
onBrushed,
|
||||
isLoading = false,
|
||||
}: Props) {
|
||||
const { charts, uiSettings, discover } = useKibana().services;
|
||||
|
@ -97,7 +101,16 @@ export function GoodBadEventsChart({
|
|||
showLegend={true}
|
||||
showLegendExtra={false}
|
||||
legendPosition={Position.Left}
|
||||
noResults={<EuiIcon type="visualizeApp" size="l" color="subdued" title="no results" />}
|
||||
noResults={
|
||||
<EuiIcon
|
||||
type="visualizeApp"
|
||||
size="l"
|
||||
color="subdued"
|
||||
title={i18n.translate('xpack.slo.goodBadEventsChart.euiIcon.noResultsLabel', {
|
||||
defaultMessage: 'no results',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
externalPointerEvents={{
|
||||
tooltip: { visible: true },
|
||||
|
@ -106,6 +119,9 @@ export function GoodBadEventsChart({
|
|||
pointerUpdateTrigger={'x'}
|
||||
locale={i18n.getLocale()}
|
||||
onElementClick={barClickHandler as ElementClickListener}
|
||||
onBrushEnd={(brushArea) => {
|
||||
onBrushed?.(getBrushData(brushArea));
|
||||
}}
|
||||
/>
|
||||
{annotation}
|
||||
<Axis
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import { BrushEvent } from '@elastic/charts';
|
||||
import { Duration, DurationUnit } from '../../typings';
|
||||
|
||||
export function toDuration(duration: string): Duration {
|
||||
|
@ -42,3 +43,10 @@ export function toCalendarAlignedMomentUnitOfTime(unit: string): moment.unitOfTi
|
|||
return 'months';
|
||||
}
|
||||
}
|
||||
|
||||
export function getBrushData(e: BrushEvent) {
|
||||
const [from, to] = [Number(e.x?.[0]), Number(e.x?.[1])];
|
||||
const [fromUtc, toUtc] = [moment(from).format(), moment(to).format()];
|
||||
|
||||
return { from, to, fromUtc, toUtc };
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
occurrencesBudgetingMethodSchema,
|
||||
timeslicesBudgetingMethodSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { getEsDateRange } from './historical_summary_client';
|
||||
import { SLO_DESTINATION_INDEX_PATTERN } from '../../common/constants';
|
||||
import { DateRange, Duration, SLODefinition } from '../domain/models';
|
||||
import { computeBurnRate, computeSLI } from '../domain/services';
|
||||
|
@ -98,7 +99,7 @@ function commonQuery(
|
|||
{ term: { 'slo.revision': slo.revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() },
|
||||
'@timestamp': getEsDateRange(dateRange),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -64,8 +64,8 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient {
|
|||
|
||||
async fetch(params: FetchHistoricalSummaryParams): Promise<HistoricalSummaryResponse> {
|
||||
const dateRangeBySlo = params.list.reduce<Record<SLOId, DateRange>>(
|
||||
(acc, { sloId, timeWindow }) => {
|
||||
acc[sloId] = getDateRange(timeWindow);
|
||||
(acc, { sloId, timeWindow, range }) => {
|
||||
acc[sloId] = range ?? getDateRange(timeWindow);
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
|
@ -272,6 +272,13 @@ function handleResultForRollingAndTimeslices(
|
|||
});
|
||||
}
|
||||
|
||||
export const getEsDateRange = (dateRange: DateRange) => {
|
||||
return {
|
||||
gte: typeof dateRange.from === 'string' ? dateRange.from : dateRange.from.toISOString(),
|
||||
lte: typeof dateRange.to === 'string' ? dateRange.to : dateRange.to.toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
function generateSearchQuery({
|
||||
sloId,
|
||||
groupBy,
|
||||
|
@ -309,10 +316,7 @@ function generateSearchQuery({
|
|||
{ term: { 'slo.revision': revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: dateRange.from.toISOString(),
|
||||
lte: dateRange.to.toISOString(),
|
||||
},
|
||||
'@timestamp': getEsDateRange(dateRange),
|
||||
},
|
||||
},
|
||||
...extraFilterByInstanceId,
|
||||
|
@ -325,7 +329,7 @@ function generateSearchQuery({
|
|||
field: '@timestamp',
|
||||
fixed_interval: fixedInterval,
|
||||
extended_bounds: {
|
||||
min: dateRange.from.toISOString(),
|
||||
min: typeof dateRange.from === 'string' ? dateRange.from : dateRange.from.toISOString(),
|
||||
max: 'now/d',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -58,7 +58,7 @@ describe('SummaryClient', () => {
|
|||
{ term: { 'slo.revision': slo.revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': { gte: expect.anything(), lt: expect.anything() },
|
||||
'@timestamp': { gte: expect.anything(), lte: expect.anything() },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -94,7 +94,7 @@ describe('SummaryClient', () => {
|
|||
range: {
|
||||
'@timestamp': {
|
||||
gte: expect.anything(),
|
||||
lt: expect.anything(),
|
||||
lte: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -136,7 +136,7 @@ describe('SummaryClient', () => {
|
|||
{ term: { 'slo.revision': slo.revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': { gte: expect.anything(), lt: expect.anything() },
|
||||
'@timestamp': { gte: expect.anything(), lte: expect.anything() },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -188,7 +188,7 @@ describe('SummaryClient', () => {
|
|||
range: {
|
||||
'@timestamp': {
|
||||
gte: expect.anything(),
|
||||
lt: expect.anything(),
|
||||
lte: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
occurrencesBudgetingMethodSchema,
|
||||
timeslicesBudgetingMethodSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { getEsDateRange } from './historical_summary_client';
|
||||
import { SLO_DESTINATION_INDEX_PATTERN } from '../../common/constants';
|
||||
import { Groupings, Meta, SLODefinition, Summary } from '../domain/models';
|
||||
import { computeSLI, computeSummaryStatus, toErrorBudget } from '../domain/services';
|
||||
|
@ -75,7 +76,7 @@ export class DefaultSummaryClient implements SummaryClient {
|
|||
{ term: { 'slo.revision': slo.revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() },
|
||||
'@timestamp': getEsDateRange(dateRange),
|
||||
},
|
||||
},
|
||||
...instanceIdFilter,
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"@kbn/data-view-field-editor-plugin",
|
||||
"@kbn/securitysolution-io-ts-utils",
|
||||
"@kbn/core-elasticsearch-server-mocks",
|
||||
"@kbn/datemath",
|
||||
"@kbn/presentation-containers",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue