mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
fix(slo): history details data (#183097)
This commit is contained in:
parent
be659de803
commit
2e9f1baf13
27 changed files with 3908 additions and 1103 deletions
|
@ -14,6 +14,7 @@ import {
|
|||
import {
|
||||
allOrAnyString,
|
||||
allOrAnyStringOrArray,
|
||||
dateRangeSchema,
|
||||
dateType,
|
||||
summarySchema,
|
||||
} from '../../schema/common';
|
||||
|
@ -33,10 +34,7 @@ const fetchHistoricalSummaryParamsSchema = t.type({
|
|||
}),
|
||||
t.partial({
|
||||
remoteName: t.string,
|
||||
range: t.type({
|
||||
from: t.string,
|
||||
to: t.string,
|
||||
}),
|
||||
range: dateRangeSchema,
|
||||
}),
|
||||
])
|
||||
),
|
||||
|
|
|
@ -13,8 +13,8 @@ const getPreviewDataParamsSchema = t.type({
|
|||
t.type({
|
||||
indicator: indicatorSchema,
|
||||
range: t.type({
|
||||
start: t.number,
|
||||
end: t.number,
|
||||
from: dateType,
|
||||
to: dateType,
|
||||
}),
|
||||
}),
|
||||
t.partial({
|
||||
|
|
|
@ -87,8 +87,8 @@ const groupSummarySchema = t.type({
|
|||
});
|
||||
|
||||
const dateRangeSchema = t.type({
|
||||
from: t.union([dateType, t.string]),
|
||||
to: t.union([dateType, t.string]),
|
||||
from: dateType,
|
||||
to: dateType,
|
||||
});
|
||||
|
||||
export {
|
||||
|
|
|
@ -64,10 +64,8 @@ export function ErrorRateChart({
|
|||
viewMode={ViewMode.VIEW}
|
||||
onBrushEnd={({ range }) => {
|
||||
onBrushed?.({
|
||||
from: range[0],
|
||||
to: range[1],
|
||||
fromUtc: moment(range[0]).format(),
|
||||
toUtc: moment(range[1]).format(),
|
||||
from: moment(range[0]).toDate(),
|
||||
to: moment(range[1]).toDate(),
|
||||
});
|
||||
}}
|
||||
noPadding
|
||||
|
|
|
@ -62,7 +62,7 @@ export const sloKeys = {
|
|||
) => [...sloKeys.all, 'burnRates', sloId, instanceId, windows] as const,
|
||||
preview: (
|
||||
indicator: Indicator,
|
||||
range: { start: number; end: number },
|
||||
range: { from: Date; to: Date },
|
||||
groupings?: Record<string, unknown>
|
||||
) => [...sloKeys.all, 'preview', indicator, range, groupings] as const,
|
||||
burnRateRules: (search: string) => [...sloKeys.all, 'burnRateRules', search],
|
||||
|
|
|
@ -24,8 +24,8 @@ export interface Params {
|
|||
sloList: SLOWithSummaryResponse[];
|
||||
shouldRefetch?: boolean;
|
||||
range?: {
|
||||
from: string;
|
||||
to: string;
|
||||
from: Date;
|
||||
to: Date;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,12 @@ export function useFetchHistoricalSummary({
|
|||
revision: slo.revision,
|
||||
objective: slo.objective,
|
||||
budgetingMethod: slo.budgetingMethod,
|
||||
range,
|
||||
range: range
|
||||
? {
|
||||
from: range?.from.toISOString(),
|
||||
to: range?.to.toISOString(),
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
|
||||
|
|
|
@ -35,7 +35,7 @@ export function useGetPreviewData({
|
|||
groupings?: Record<string, unknown>;
|
||||
objective?: Objective;
|
||||
indicator: Indicator;
|
||||
range: { start: number; end: number };
|
||||
range: { from: Date; to: Date };
|
||||
}): UseGetPreviewData {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ 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 { getBrushTimeBounds } from '../../../utils/slo/duration';
|
||||
import { SloTabId } from './slo_details';
|
||||
import { useGetPreviewData } from '../../../hooks/use_get_preview_data';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
@ -47,10 +47,7 @@ import { getDiscoverLink } from '../../../utils/slo/get_discover_link';
|
|||
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse;
|
||||
range: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
range: { from: Date; to: Date };
|
||||
selectedTabId: SloTabId;
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
@ -234,7 +231,7 @@ export function EventsChartPanel({ slo, range, selectedTabId, onBrushed }: Props
|
|||
pointerUpdateTrigger={'x'}
|
||||
locale={i18n.getLocale()}
|
||||
onBrushEnd={(brushArea) => {
|
||||
onBrushed?.(getBrushData(brushArea));
|
||||
onBrushed?.(getBrushTimeBounds(brushArea));
|
||||
}}
|
||||
/>
|
||||
{annotation}
|
||||
|
|
|
@ -18,12 +18,10 @@ export interface Props {
|
|||
slo: SLOWithSummaryResponse;
|
||||
isAutoRefreshing: boolean;
|
||||
selectedTabId: SloTabId;
|
||||
range?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
range?: { from: Date; to: Date };
|
||||
onBrushed?: (timeBounds: TimeBounds) => void;
|
||||
}
|
||||
|
||||
export function HistoricalDataCharts({
|
||||
slo,
|
||||
range,
|
||||
|
|
|
@ -4,23 +4,25 @@
|
|||
* 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,
|
||||
EuiSuperDatePicker,
|
||||
OnRefreshProps,
|
||||
OnTimeChangeProps,
|
||||
} 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 { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { BurnRates } from '../../../../components/slo/burn_rate/burn_rates';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { useBurnRateOptions } from '../../hooks/use_burn_rate_options';
|
||||
import { TimeBounds } from '../../types';
|
||||
import { EventsChartPanel } from '../events_chart_panel';
|
||||
import { HistoricalDataCharts } from '../historical_data_charts';
|
||||
import { SloTabId } from '../slo_details';
|
||||
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse;
|
||||
isAutoRefreshing: boolean;
|
||||
|
@ -29,10 +31,8 @@ export interface Props {
|
|||
|
||||
export function SLODetailsHistory({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
||||
const { uiSettings } = useKibana().services;
|
||||
|
||||
const { burnRateOptions } = useBurnRateOptions(slo);
|
||||
|
||||
const [start, setStart] = useState('now-30d');
|
||||
const [start, setStart] = useState(`now-${slo.timeWindow.duration}`);
|
||||
const [end, setEnd] = useState('now');
|
||||
|
||||
const onTimeChange = (val: OnTimeChangeProps) => {
|
||||
|
@ -42,19 +42,17 @@ export function SLODetailsHistory({ slo, isAutoRefreshing, selectedTabId }: Prop
|
|||
|
||||
const onRefresh = (val: OnRefreshProps) => {};
|
||||
|
||||
const absRange = useMemo(() => {
|
||||
const range = 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);
|
||||
}, []);
|
||||
const onBrushed = ({ from, to }: TimeBounds) => {
|
||||
setStart(from.toISOString());
|
||||
setEnd(to.toISOString());
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -94,10 +92,7 @@ export function SLODetailsHistory({ slo, isAutoRefreshing, selectedTabId }: Prop
|
|||
isAutoRefreshing={isAutoRefreshing}
|
||||
burnRateOptions={burnRateOptions}
|
||||
selectedTabId={selectedTabId}
|
||||
range={{
|
||||
from: absRange.from,
|
||||
to: absRange.to,
|
||||
}}
|
||||
range={range}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -105,19 +100,13 @@ export function SLODetailsHistory({ slo, isAutoRefreshing, selectedTabId }: Prop
|
|||
slo={slo}
|
||||
selectedTabId={selectedTabId}
|
||||
isAutoRefreshing={isAutoRefreshing}
|
||||
range={{
|
||||
from: start,
|
||||
to: end,
|
||||
}}
|
||||
range={range}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
<EuiFlexItem>
|
||||
<EventsChartPanel
|
||||
slo={slo}
|
||||
range={{
|
||||
start: absRange.absoluteFrom,
|
||||
end: absRange.absoluteTo,
|
||||
}}
|
||||
range={range}
|
||||
selectedTabId={selectedTabId}
|
||||
onBrushed={onBrushed}
|
||||
/>
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
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 { useBurnRateOptions } from '../hooks/use_burn_rate_options';
|
||||
import { EventsChartPanel } from './events_chart_panel';
|
||||
import { HistoricalDataCharts } from './historical_data_charts';
|
||||
import { SLODetailsHistory } from './history/slo_details_history';
|
||||
import { Overview } from './overview/overview';
|
||||
import { SloDetailsAlerts } from './slo_detail_alerts';
|
||||
import { SloHealthCallout } from './slo_health_callout';
|
||||
|
@ -22,7 +23,6 @@ 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;
|
||||
|
||||
export type SloTabId = typeof OVERVIEW_TAB_ID | typeof ALERTS_TAB_ID | typeof HISTORY_TAB_ID;
|
||||
|
||||
|
@ -34,16 +34,16 @@ export interface Props {
|
|||
export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
|
||||
const { burnRateOptions } = useBurnRateOptions(slo);
|
||||
|
||||
const [range, setRange] = useState({
|
||||
start: new Date().getTime() - DAY_IN_MILLISECONDS,
|
||||
end: new Date().getTime(),
|
||||
const [range, setRange] = useState<{ from: Date; to: Date }>({
|
||||
from: moment().subtract(1, 'day').toDate(),
|
||||
to: new Date(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let intervalId: any;
|
||||
if (isAutoRefreshing) {
|
||||
intervalId = setInterval(() => {
|
||||
setRange({ start: new Date().getTime() - DAY_IN_MILLISECONDS, end: new Date().getTime() });
|
||||
setRange({ from: moment().subtract(1, 'day').toDate(), to: new Date() });
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import moment from 'moment';
|
|||
import React, { useRef } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getBrushData } from '../../../utils/slo/duration';
|
||||
import { getBrushTimeBounds } from '../../../utils/slo/duration';
|
||||
import { TimeBounds } from '../types';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { ChartData } from '../../../typings';
|
||||
|
@ -84,7 +84,7 @@ export function WideChart({ chart, data, id, isLoading, state, onBrushed }: Prop
|
|||
pointerUpdateTrigger={'x'}
|
||||
locale={i18n.getLocale()}
|
||||
onBrushEnd={(brushArea) => {
|
||||
onBrushed?.(getBrushData(brushArea));
|
||||
onBrushed?.(getBrushTimeBounds(brushArea));
|
||||
}}
|
||||
/>
|
||||
<Axis
|
||||
|
|
|
@ -11,8 +11,6 @@ export interface SloDetailsPathParams {
|
|||
}
|
||||
|
||||
export interface TimeBounds {
|
||||
from: number;
|
||||
to: number;
|
||||
fromUtc: string;
|
||||
toUtc: string;
|
||||
from: Date;
|
||||
to: Date;
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ import { max, min } from 'lodash';
|
|||
import moment from 'moment';
|
||||
import React, { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { GoodBadEventsChart } from '../../../slos/components/common/good_bad_events_chart';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { GoodBadEventsChart } from '../../../slos/components/common/good_bad_events_chart';
|
||||
import { useDebouncedGetPreviewData } from '../../hooks/use_preview';
|
||||
import { useSectionFormValidation } from '../../hooks/use_section_form_validation';
|
||||
import { CreateSLOForm } from '../../types';
|
||||
|
@ -53,13 +53,11 @@ interface DataPreviewChartProps {
|
|||
useGoodBadEventsChart?: boolean;
|
||||
label?: string;
|
||||
range?: {
|
||||
start: number;
|
||||
end: number;
|
||||
from: Date;
|
||||
to: Date;
|
||||
};
|
||||
}
|
||||
|
||||
const ONE_HOUR_IN_MILLISECONDS = 1 * 60 * 60 * 1000;
|
||||
|
||||
export function DataPreviewChart({
|
||||
formatPattern,
|
||||
threshold,
|
||||
|
@ -81,8 +79,8 @@ export function DataPreviewChart({
|
|||
});
|
||||
|
||||
const [defaultRange, _] = useState({
|
||||
start: new Date().getTime() - ONE_HOUR_IN_MILLISECONDS,
|
||||
end: new Date().getTime(),
|
||||
from: moment().subtract(1, 'hour').toDate(),
|
||||
to: new Date(),
|
||||
});
|
||||
|
||||
const indicator = watch('indicator');
|
||||
|
@ -92,7 +90,7 @@ export function DataPreviewChart({
|
|||
isLoading: isPreviewLoading,
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useDebouncedGetPreviewData(isIndicatorSectionValid, indicator, range || defaultRange);
|
||||
} = useDebouncedGetPreviewData(isIndicatorSectionValid, indicator, range ?? defaultRange);
|
||||
|
||||
const isMoreThan100 =
|
||||
!ignoreMoreThan100 && previewData?.find((row) => row.sliValue && row.sliValue > 1) != null;
|
||||
|
|
|
@ -4,25 +4,24 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
import { FilterStateStore } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
ALL_VALUE,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
QuerySchema,
|
||||
FiltersSchema,
|
||||
QuerySchema,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
} from '@kbn/slo-schema';
|
||||
import { FilterStateStore } from '@kbn/es-query';
|
||||
import moment from 'moment';
|
||||
import React, { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { FieldSelector } from '../synthetics_common/field_selector';
|
||||
import { formatAllFilters } from '../../helpers/format_filters';
|
||||
import { CreateSLOForm } from '../../types';
|
||||
import { DataPreviewChart } from '../common/data_preview_chart';
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
import { GroupByCardinality } from '../common/group_by_cardinality';
|
||||
import { formatAllFilters } from '../../helpers/format_filters';
|
||||
|
||||
const ONE_DAY_IN_MILLISECONDS = 1 * 60 * 60 * 1000 * 24;
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
import { FieldSelector } from '../synthetics_common/field_selector';
|
||||
|
||||
export function SyntheticsAvailabilityIndicatorTypeForm() {
|
||||
const { watch } = useFormContext<CreateSLOForm<SyntheticsAvailabilityIndicator>>();
|
||||
|
@ -36,8 +35,8 @@ export function SyntheticsAvailabilityIndicatorTypeForm() {
|
|||
]);
|
||||
|
||||
const [range, _] = useState({
|
||||
start: new Date().getTime() - ONE_DAY_IN_MILLISECONDS,
|
||||
end: new Date().getTime(),
|
||||
from: moment().subtract(1, 'day').toDate(),
|
||||
to: new Date(),
|
||||
});
|
||||
|
||||
const filters = {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useGetPreviewData } from '../../../hooks/use_get_preview_data';
|
|||
export function useDebouncedGetPreviewData(
|
||||
isIndicatorValid: boolean,
|
||||
indicator: Indicator,
|
||||
range: { start: number; end: number }
|
||||
range: { from: Date; to: Date }
|
||||
) {
|
||||
const serializedIndicator = JSON.stringify(indicator);
|
||||
const [indicatorState, setIndicatorState] = useState<string>(serializedIndicator);
|
||||
|
|
|
@ -24,7 +24,7 @@ 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 { getBrushTimeBounds } from '../../../../utils/slo/duration';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { openInDiscover } from '../../../../utils/slo/get_discover_link';
|
||||
|
||||
|
@ -120,7 +120,7 @@ export function GoodBadEventsChart({
|
|||
locale={i18n.getLocale()}
|
||||
onElementClick={barClickHandler as ElementClickListener}
|
||||
onBrushEnd={(brushArea) => {
|
||||
onBrushed?.(getBrushData(brushArea));
|
||||
onBrushed?.(getBrushTimeBounds(brushArea));
|
||||
}}
|
||||
/>
|
||||
{annotation}
|
||||
|
|
|
@ -9,6 +9,7 @@ import moment from 'moment';
|
|||
import { assertNever } from '@kbn/std';
|
||||
import { BrushEvent } from '@elastic/charts';
|
||||
import { Duration, DurationUnit } from '../../typings';
|
||||
import { TimeBounds } from '../../pages/slo_details/types';
|
||||
|
||||
export function toDuration(duration: string): Duration {
|
||||
const durationValue = duration.substring(0, duration.length - 1);
|
||||
|
@ -44,9 +45,9 @@ export function toCalendarAlignedMomentUnitOfTime(unit: string): moment.unitOfTi
|
|||
}
|
||||
}
|
||||
|
||||
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()];
|
||||
export function getBrushTimeBounds(e: BrushEvent): TimeBounds {
|
||||
const from = moment(Number(e.x?.[0])).toDate();
|
||||
const to = moment(Number(e.x?.[1])).toDate();
|
||||
|
||||
return { from, to, fromUtc, toUtc };
|
||||
return { from, to };
|
||||
}
|
||||
|
|
|
@ -12,12 +12,13 @@ import {
|
|||
deleteSLOInstancesParamsSchema,
|
||||
deleteSLOParamsSchema,
|
||||
fetchHistoricalSummaryParamsSchema,
|
||||
fetchHistoricalSummaryResponseSchema,
|
||||
fetchSLOHealthParamsSchema,
|
||||
findSloDefinitionsParamsSchema,
|
||||
findSLOGroupsParamsSchema,
|
||||
findSLOParamsSchema,
|
||||
getPreviewDataParamsSchema,
|
||||
getSLOBurnRatesParamsSchema,
|
||||
fetchSLOHealthParamsSchema,
|
||||
getSLOInstancesParamsSchema,
|
||||
getSLOParamsSchema,
|
||||
manageSLOParamsSchema,
|
||||
|
@ -44,7 +45,6 @@ import {
|
|||
KibanaSavedObjectsSLORepository,
|
||||
UpdateSLO,
|
||||
} from '../../services';
|
||||
import { FetchHistoricalSummary } from '../../services/fetch_historical_summary';
|
||||
import { FindSLODefinitions } from '../../services/find_slo_definitions';
|
||||
import { getBurnRates } from '../../services/get_burn_rates';
|
||||
import { getGlobalDiagnosis } from '../../services/get_diagnosis';
|
||||
|
@ -513,9 +513,10 @@ const fetchHistoricalSummary = createSloServerRoute({
|
|||
|
||||
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const historicalSummaryClient = new DefaultHistoricalSummaryClient(esClient);
|
||||
const fetchSummaryData = new FetchHistoricalSummary(historicalSummaryClient);
|
||||
|
||||
return await fetchSummaryData.execute(params.body);
|
||||
const historicalSummary = await historicalSummaryClient.fetch(params.body);
|
||||
|
||||
return fetchHistoricalSummaryResponseSchema.encode(historicalSummary);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,7 +19,6 @@ 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';
|
||||
|
@ -99,7 +98,7 @@ function commonQuery(
|
|||
{ term: { 'slo.revision': slo.revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': getEsDateRange(dateRange),
|
||||
'@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,25 +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 {
|
||||
FetchHistoricalSummaryParams,
|
||||
FetchHistoricalSummaryResponse,
|
||||
fetchHistoricalSummaryResponseSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { HistoricalSummaryClient } from './historical_summary_client';
|
||||
|
||||
export class FetchHistoricalSummary {
|
||||
constructor(private historicalSummaryClient: HistoricalSummaryClient) {}
|
||||
|
||||
public async execute(
|
||||
params: FetchHistoricalSummaryParams
|
||||
): Promise<FetchHistoricalSummaryResponse> {
|
||||
const historicalSummary = await this.historicalSummaryClient.fetch(params);
|
||||
|
||||
return fetchHistoricalSummaryResponseSchema.encode(historicalSummary);
|
||||
}
|
||||
}
|
|
@ -592,20 +592,19 @@ export class GetPreviewData {
|
|||
// Timeslice metric so that the chart is as close to the evaluation as possible.
|
||||
// Otherwise due to how the statistics work, the values might not look like
|
||||
// they've breached the threshold.
|
||||
const rangeDuration = moment(params.range.to).diff(params.range.from, 'ms');
|
||||
const bucketSize =
|
||||
params.indicator.type === 'sli.metric.timeslice' &&
|
||||
params.range.end - params.range.start <= 86_400_000 &&
|
||||
rangeDuration <= 86_400_000 &&
|
||||
params.objective?.timesliceWindow
|
||||
? params.objective.timesliceWindow.asMinutes()
|
||||
: Math.max(
|
||||
calculateAuto
|
||||
.near(100, moment.duration(params.range.end - params.range.start, 'ms'))
|
||||
?.asMinutes() ?? 0,
|
||||
calculateAuto.near(100, moment.duration(rangeDuration, 'ms'))?.asMinutes() ?? 0,
|
||||
1
|
||||
);
|
||||
const options: Options = {
|
||||
instanceId: params.instanceId,
|
||||
range: params.range,
|
||||
range: { start: params.range.from.getTime(), end: params.range.to.getTime() },
|
||||
groupBy: params.groupBy,
|
||||
remoteName: params.remoteName,
|
||||
groupings: params.groupings,
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import { ElasticsearchClientMock, elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
||||
import { ALL_VALUE } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import { oneMinute, oneMonth, thirtyDays } from './fixtures/duration';
|
||||
import { DateRange, SLODefinition } from '../domain/models';
|
||||
import { oneMinute, oneMonth, sevenDays, thirtyDays } from './fixtures/duration';
|
||||
import { createSLO } from './fixtures/slo';
|
||||
import {
|
||||
DefaultHistoricalSummaryClient,
|
||||
|
@ -29,16 +30,33 @@ const commonEsResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
const generateEsResponseForRollingSLO = (
|
||||
rollingDays: number = 30,
|
||||
good: number = 97,
|
||||
total: number = 100
|
||||
) => {
|
||||
const { fixedInterval, bucketsPerDay } = getFixedIntervalAndBucketsPerDay(rollingDays);
|
||||
const numberOfBuckets = rollingDays * bucketsPerDay;
|
||||
const doubleDuration = rollingDays * 2;
|
||||
const startDay = moment.utc().subtract(doubleDuration, 'day').startOf('day');
|
||||
const bucketSize = fixedInterval === '1d' ? 24 : Number(fixedInterval.slice(0, -1));
|
||||
const MINUTES_IN_DAY = 1440;
|
||||
|
||||
const generateEsResponseForRollingSLO = (slo: SLODefinition, overridedRange?: DateRange) => {
|
||||
const rollingDurationInDays = slo.timeWindow.duration.asMinutes() / MINUTES_IN_DAY;
|
||||
const timesliceInMin = slo.objective.timesliceWindow?.asMinutes();
|
||||
const overridedRangeInDays = overridedRange
|
||||
? moment(overridedRange.to).diff(moment(overridedRange.from), 'days')
|
||||
: 0;
|
||||
|
||||
const { fixedInterval, bucketsPerDay } = getFixedIntervalAndBucketsPerDay(
|
||||
overridedRangeInDays ? overridedRangeInDays : rollingDurationInDays
|
||||
);
|
||||
const fullDuration = overridedRange
|
||||
? rollingDurationInDays + overridedRangeInDays
|
||||
: rollingDurationInDays * 2;
|
||||
const numberOfBuckets = fullDuration * bucketsPerDay;
|
||||
const startDay = moment().subtract(fullDuration, 'day').startOf('day');
|
||||
const bucketSizeInHour = moment
|
||||
.duration(
|
||||
fixedInterval.slice(0, -1),
|
||||
fixedInterval.slice(-1) as moment.unitOfTime.DurationConstructor
|
||||
)
|
||||
.asHours();
|
||||
|
||||
const good = timesliceInMin ? Math.floor(((bucketSizeInHour * 60) / timesliceInMin) * 0.97) : 97;
|
||||
const total = timesliceInMin ? Math.floor((bucketSizeInHour * 60) / timesliceInMin) : 100;
|
||||
|
||||
return {
|
||||
...commonEsResponse,
|
||||
responses: [
|
||||
|
@ -51,11 +69,11 @@ const generateEsResponseForRollingSLO = (
|
|||
.map((_, index) => ({
|
||||
key_as_string: startDay
|
||||
.clone()
|
||||
.add(index * bucketSize, 'hours')
|
||||
.add(index * bucketSizeInHour, 'hours')
|
||||
.toISOString(),
|
||||
key: startDay
|
||||
.clone()
|
||||
.add(index * bucketSize, 'hours')
|
||||
.add(index * bucketSizeInHour, 'hours')
|
||||
.format('x'),
|
||||
doc_count: 1440,
|
||||
total: {
|
||||
|
@ -65,10 +83,16 @@ const generateEsResponseForRollingSLO = (
|
|||
value: good,
|
||||
},
|
||||
cumulative_good: {
|
||||
value: good * (index + 1),
|
||||
value:
|
||||
index < rollingDurationInDays * bucketsPerDay
|
||||
? good * (index + 1)
|
||||
: good * rollingDurationInDays * bucketsPerDay,
|
||||
},
|
||||
cumulative_total: {
|
||||
value: total * (index + 1),
|
||||
value:
|
||||
index < rollingDurationInDays * bucketsPerDay
|
||||
? total * (index + 1)
|
||||
: total * rollingDurationInDays * bucketsPerDay,
|
||||
},
|
||||
})),
|
||||
},
|
||||
|
@ -137,13 +161,13 @@ describe('FetchHistoricalSummary', () => {
|
|||
});
|
||||
|
||||
describe('Rolling and Occurrences SLOs', () => {
|
||||
it('returns the summary', async () => {
|
||||
it('returns the summary using the SLO timeWindow date range', async () => {
|
||||
const slo = createSLO({
|
||||
timeWindow: { type: 'rolling', duration: thirtyDays() },
|
||||
objective: { target: 0.95 },
|
||||
groupBy: ALL_VALUE,
|
||||
});
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(30));
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(slo));
|
||||
const client = new DefaultHistoricalSummaryClient(esClientMock);
|
||||
|
||||
const results = await client.fetch({
|
||||
|
@ -163,20 +187,52 @@ describe('FetchHistoricalSummary', () => {
|
|||
results[0].data.forEach((dailyResult) =>
|
||||
expect(dailyResult).toMatchSnapshot({ date: expect.any(Date) })
|
||||
);
|
||||
});
|
||||
|
||||
expect(results[0].data).toHaveLength(180);
|
||||
it('returns the summary using the provided date range', async () => {
|
||||
const slo = createSLO({
|
||||
timeWindow: { type: 'rolling', duration: sevenDays() },
|
||||
objective: { target: 0.9 },
|
||||
groupBy: ALL_VALUE,
|
||||
});
|
||||
const range: DateRange = {
|
||||
from: new Date('2023-01-09T15:00:00.000Z'),
|
||||
to: new Date('2023-01-13T15:00:00.000Z'),
|
||||
};
|
||||
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(slo, range));
|
||||
const client = new DefaultHistoricalSummaryClient(esClientMock);
|
||||
|
||||
const results = await client.fetch({
|
||||
list: [
|
||||
{
|
||||
timeWindow: slo.timeWindow,
|
||||
groupBy: slo.groupBy,
|
||||
budgetingMethod: slo.budgetingMethod,
|
||||
objective: slo.objective,
|
||||
revision: slo.revision,
|
||||
sloId: slo.id,
|
||||
instanceId: ALL_VALUE,
|
||||
range,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
results[0].data.forEach((dailyResult) =>
|
||||
expect(dailyResult).toMatchSnapshot({ date: expect.any(Date) })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rolling and Timeslices SLOs', () => {
|
||||
it('returns the summary', async () => {
|
||||
it('returns the summary using the SLO timeWindow date range', async () => {
|
||||
const slo = createSLO({
|
||||
timeWindow: { type: 'rolling', duration: thirtyDays() },
|
||||
budgetingMethod: 'timeslices',
|
||||
objective: { target: 0.95, timesliceTarget: 0.9, timesliceWindow: oneMinute() },
|
||||
groupBy: ALL_VALUE,
|
||||
});
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(30));
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(slo));
|
||||
const client = new DefaultHistoricalSummaryClient(esClientMock);
|
||||
|
||||
const results = await client.fetch({
|
||||
|
@ -198,6 +254,40 @@ describe('FetchHistoricalSummary', () => {
|
|||
);
|
||||
expect(results[0].data).toHaveLength(180);
|
||||
});
|
||||
|
||||
it('returns the summary using the provided date range', async () => {
|
||||
const slo = createSLO({
|
||||
timeWindow: { type: 'rolling', duration: thirtyDays() },
|
||||
budgetingMethod: 'timeslices',
|
||||
objective: { target: 0.95, timesliceTarget: 0.9, timesliceWindow: oneMinute() },
|
||||
groupBy: ALL_VALUE,
|
||||
});
|
||||
const range: DateRange = {
|
||||
from: new Date('2023-01-09T15:00:00.000Z'),
|
||||
to: new Date('2023-01-13T15:00:00.000Z'),
|
||||
};
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(slo, range));
|
||||
const client = new DefaultHistoricalSummaryClient(esClientMock);
|
||||
|
||||
const results = await client.fetch({
|
||||
list: [
|
||||
{
|
||||
timeWindow: slo.timeWindow,
|
||||
groupBy: slo.groupBy,
|
||||
budgetingMethod: slo.budgetingMethod,
|
||||
objective: slo.objective,
|
||||
revision: slo.revision,
|
||||
sloId: slo.id,
|
||||
instanceId: ALL_VALUE,
|
||||
range,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
results[0].data.forEach((dailyResult) =>
|
||||
expect(dailyResult).toMatchSnapshot({ date: expect.any(Date) })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Calendar Aligned and Timeslices SLOs', () => {
|
||||
|
@ -275,7 +365,7 @@ describe('FetchHistoricalSummary', () => {
|
|||
objective: { target: 0.95 },
|
||||
groupBy: 'host',
|
||||
});
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(30));
|
||||
esClientMock.msearch.mockResolvedValueOnce(generateEsResponseForRollingSLO(slo));
|
||||
const client = new DefaultHistoricalSummaryClient(esClientMock);
|
||||
|
||||
const results = await client.fetch({
|
||||
|
|
|
@ -63,13 +63,12 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient {
|
|||
constructor(private esClient: ElasticsearchClient) {}
|
||||
|
||||
async fetch(params: FetchHistoricalSummaryParams): Promise<HistoricalSummaryResponse> {
|
||||
const dateRangeBySlo = params.list.reduce<Record<SLOId, DateRange>>(
|
||||
(acc, { sloId, timeWindow, range }) => {
|
||||
acc[sloId] = range ?? getDateRange(timeWindow);
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const dateRangeBySlo = params.list.reduce<
|
||||
Record<SLOId, { range: DateRange; queryRange: DateRange }>
|
||||
>((acc, { sloId, timeWindow, range }) => {
|
||||
acc[sloId] = getDateRange(timeWindow, range);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const searches = params.list.flatMap(
|
||||
({ sloId, revision, budgetingMethod, instanceId, groupBy, timeWindow, remoteName }) => [
|
||||
|
@ -113,7 +112,12 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient {
|
|||
historicalSummary.push({
|
||||
sloId,
|
||||
instanceId,
|
||||
data: handleResultForRollingAndTimeslices(objective, timeWindow, buckets),
|
||||
data: handleResultForRollingAndTimeslices(
|
||||
objective,
|
||||
timeWindow,
|
||||
buckets,
|
||||
dateRangeBySlo[sloId]
|
||||
),
|
||||
});
|
||||
|
||||
continue;
|
||||
|
@ -123,7 +127,7 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient {
|
|||
historicalSummary.push({
|
||||
sloId,
|
||||
instanceId,
|
||||
data: handleResultForRollingAndOccurrences(objective, timeWindow, buckets),
|
||||
data: handleResultForRollingAndOccurrences(objective, buckets, dateRangeBySlo[sloId]),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
@ -187,10 +191,10 @@ function handleResultForCalendarAlignedAndOccurrences(
|
|||
function handleResultForCalendarAlignedAndTimeslices(
|
||||
objective: Objective,
|
||||
buckets: DailyAggBucket[],
|
||||
dateRange: DateRange
|
||||
dateRange: { range: DateRange; queryRange: DateRange }
|
||||
): HistoricalSummary[] {
|
||||
const initialErrorBudget = 1 - objective.target;
|
||||
const totalSlices = computeTotalSlicesFromDateRange(dateRange, objective.timesliceWindow!);
|
||||
const totalSlices = computeTotalSlicesFromDateRange(dateRange.range, objective.timesliceWindow!);
|
||||
|
||||
return buckets.map((bucket: DailyAggBucket): HistoricalSummary => {
|
||||
const good = bucket.cumulative_good?.value ?? 0;
|
||||
|
@ -210,18 +214,17 @@ function handleResultForCalendarAlignedAndTimeslices(
|
|||
|
||||
function handleResultForRollingAndOccurrences(
|
||||
objective: Objective,
|
||||
timeWindow: TimeWindow,
|
||||
buckets: DailyAggBucket[]
|
||||
buckets: DailyAggBucket[],
|
||||
dateRange: { range: DateRange; queryRange: DateRange }
|
||||
): HistoricalSummary[] {
|
||||
const initialErrorBudget = 1 - objective.target;
|
||||
const rollingWindowDurationInDays = moment
|
||||
.duration(timeWindow.duration.value, toMomentUnitOfTime(timeWindow.duration.unit))
|
||||
.asDays();
|
||||
|
||||
const { bucketsPerDay } = getFixedIntervalAndBucketsPerDay(rollingWindowDurationInDays);
|
||||
|
||||
return buckets
|
||||
.slice(-bucketsPerDay * rollingWindowDurationInDays)
|
||||
.filter(
|
||||
(bucket) =>
|
||||
moment(bucket.key_as_string).isSameOrAfter(dateRange.range.from) &&
|
||||
moment(bucket.key_as_string).isSameOrBefore(dateRange.range.to)
|
||||
)
|
||||
.map((bucket: DailyAggBucket): HistoricalSummary => {
|
||||
const good = bucket.cumulative_good?.value ?? 0;
|
||||
const total = bucket.cumulative_total?.value ?? 0;
|
||||
|
@ -242,20 +245,21 @@ function handleResultForRollingAndOccurrences(
|
|||
function handleResultForRollingAndTimeslices(
|
||||
objective: Objective,
|
||||
timeWindow: TimeWindow,
|
||||
buckets: DailyAggBucket[]
|
||||
buckets: DailyAggBucket[],
|
||||
dateRange: { range: DateRange; queryRange: DateRange }
|
||||
): HistoricalSummary[] {
|
||||
const initialErrorBudget = 1 - objective.target;
|
||||
const rollingWindowDurationInDays = moment
|
||||
.duration(timeWindow.duration.value, toMomentUnitOfTime(timeWindow.duration.unit))
|
||||
.asDays();
|
||||
|
||||
const { bucketsPerDay } = getFixedIntervalAndBucketsPerDay(rollingWindowDurationInDays);
|
||||
const totalSlices = Math.ceil(
|
||||
timeWindow.duration.asSeconds() / objective.timesliceWindow!.asSeconds()
|
||||
);
|
||||
|
||||
return buckets
|
||||
.slice(-bucketsPerDay * rollingWindowDurationInDays)
|
||||
.filter(
|
||||
(bucket) =>
|
||||
moment(bucket.key_as_string).isSameOrAfter(dateRange.range.from) &&
|
||||
moment(bucket.key_as_string).isSameOrBefore(dateRange.range.to)
|
||||
)
|
||||
.map((bucket: DailyAggBucket): HistoricalSummary => {
|
||||
const good = bucket.cumulative_good?.value ?? 0;
|
||||
const total = bucket.cumulative_total?.value ?? 0;
|
||||
|
@ -272,13 +276,6 @@ 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,
|
||||
|
@ -292,15 +289,19 @@ function generateSearchQuery({
|
|||
sloId: string;
|
||||
groupBy: GroupBy;
|
||||
revision: number;
|
||||
dateRange: DateRange;
|
||||
dateRange: { range: DateRange; queryRange: DateRange };
|
||||
timeWindow: TimeWindow;
|
||||
budgetingMethod: BudgetingMethod;
|
||||
}): MsearchMultisearchBody {
|
||||
const unit = toMomentUnitOfTime(timeWindow.duration.unit);
|
||||
const timeWindowDurationInDays = moment.duration(timeWindow.duration.value, unit).asDays();
|
||||
|
||||
const queryRangeDurationInDays = Math.ceil(
|
||||
moment(dateRange.range.to).diff(dateRange.range.from, 'days')
|
||||
);
|
||||
|
||||
const { fixedInterval, bucketsPerDay } =
|
||||
getFixedIntervalAndBucketsPerDay(timeWindowDurationInDays);
|
||||
getFixedIntervalAndBucketsPerDay(queryRangeDurationInDays);
|
||||
|
||||
const extraFilterByInstanceId =
|
||||
!!groupBy && ![groupBy].flat().includes(ALL_VALUE) && instanceId !== ALL_VALUE
|
||||
|
@ -316,7 +317,10 @@ function generateSearchQuery({
|
|||
{ term: { 'slo.revision': revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': getEsDateRange(dateRange),
|
||||
'@timestamp': {
|
||||
gte: dateRange.queryRange.from.toISOString(),
|
||||
lte: dateRange.queryRange.to.toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
...extraFilterByInstanceId,
|
||||
|
@ -329,8 +333,8 @@ function generateSearchQuery({
|
|||
field: '@timestamp',
|
||||
fixed_interval: fixedInterval,
|
||||
extended_bounds: {
|
||||
min: typeof dateRange.from === 'string' ? dateRange.from : dateRange.from.toISOString(),
|
||||
max: 'now/d',
|
||||
min: dateRange.queryRange.from.toISOString(),
|
||||
max: dateRange.queryRange.to.toISOString(),
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
|
@ -382,26 +386,62 @@ function generateSearchQuery({
|
|||
};
|
||||
}
|
||||
|
||||
function getDateRange(timeWindow: TimeWindow) {
|
||||
/**
|
||||
* queryRange is used for the filter range on the query,
|
||||
* while range is used for storing the actual range requested
|
||||
* For a rolling window, the query range starts 1 timeWindow duration before the actual range from.
|
||||
* For calednar window, the query range is the same as the range.
|
||||
*
|
||||
* @param timeWindow
|
||||
* @param range
|
||||
* @returns the request {range} and the query range {queryRange}
|
||||
*
|
||||
*/
|
||||
function getDateRange(
|
||||
timeWindow: TimeWindow,
|
||||
range?: DateRange
|
||||
): { range: DateRange; queryRange: DateRange } {
|
||||
if (rollingTimeWindowSchema.is(timeWindow)) {
|
||||
const unit = toMomentUnitOfTime(timeWindow.duration.unit as DurationUnit);
|
||||
|
||||
if (range) {
|
||||
return {
|
||||
range,
|
||||
queryRange: {
|
||||
from: moment(range.from)
|
||||
.subtract(timeWindow.duration.value, unit)
|
||||
.startOf('day')
|
||||
.toDate(),
|
||||
to: moment(range.to).startOf('minute').toDate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const now = moment();
|
||||
return {
|
||||
from: now
|
||||
.clone()
|
||||
.subtract(timeWindow.duration.value * 2, unit)
|
||||
.startOf('day')
|
||||
.toDate(),
|
||||
to: now.startOf('minute').toDate(),
|
||||
range: {
|
||||
from: now.clone().subtract(timeWindow.duration.value, unit).startOf('day').toDate(),
|
||||
to: now.clone().startOf('minute').toDate(),
|
||||
},
|
||||
queryRange: {
|
||||
from: now
|
||||
.clone()
|
||||
.subtract(timeWindow.duration.value * 2, unit)
|
||||
.startOf('day')
|
||||
.toDate(),
|
||||
to: now.clone().startOf('minute').toDate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (calendarAlignedTimeWindowSchema.is(timeWindow)) {
|
||||
const now = moment();
|
||||
const unit = toCalendarAlignedTimeWindowMomentUnit(timeWindow);
|
||||
const from = moment.utc(now).startOf(unit);
|
||||
const to = moment.utc(now).endOf(unit);
|
||||
|
||||
return { from: from.toDate(), to: to.toDate() };
|
||||
const calendarRange = { from: from.toDate(), to: to.toDate() };
|
||||
return { range: calendarRange, queryRange: calendarRange };
|
||||
}
|
||||
|
||||
assertNever(timeWindow);
|
||||
|
@ -411,6 +451,9 @@ export function getFixedIntervalAndBucketsPerDay(durationInDays: number): {
|
|||
fixedInterval: string;
|
||||
bucketsPerDay: number;
|
||||
} {
|
||||
if (durationInDays <= 3) {
|
||||
return { fixedInterval: '30m', bucketsPerDay: 48 };
|
||||
}
|
||||
if (durationInDays <= 7) {
|
||||
return { fixedInterval: '1h', bucketsPerDay: 24 };
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
export * from './create_slo';
|
||||
export * from './delete_slo';
|
||||
export * from './delete_slo_instances';
|
||||
export * from './fetch_historical_summary';
|
||||
export * from './find_slo';
|
||||
export * from './get_slo';
|
||||
export * from './historical_summary_client';
|
||||
|
|
|
@ -16,7 +16,6 @@ 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';
|
||||
|
@ -76,7 +75,10 @@ export class DefaultSummaryClient implements SummaryClient {
|
|||
{ term: { 'slo.revision': slo.revision } },
|
||||
{
|
||||
range: {
|
||||
'@timestamp': getEsDateRange(dateRange),
|
||||
'@timestamp': {
|
||||
gte: dateRange.from.toISOString(),
|
||||
lte: dateRange.to.toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
...instanceIdFilter,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue