feat(slo): render dynamic overview section based on indicator type (#161299)

This commit is contained in:
Kevin Delemme 2023-07-11 17:01:42 -04:00 committed by GitHub
parent 6673ffbf18
commit 9d711fb944
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 301 additions and 208 deletions

View file

@ -70,15 +70,8 @@ export function HeaderControl({ isLoading, slo }: Props) {
const handleNavigateToRules = async () => {
const locator = locators.get<RulesParams>(rulesLocatorID);
if (slo?.id) {
locator?.navigate(
{
params: { sloId: slo.id },
},
{
replace: false,
}
);
if (slo?.id && locator) {
locator.navigate({ params: { sloId: slo.id } }, { replace: false });
}
};
@ -250,7 +243,7 @@ export function HeaderControl({ isLoading, slo }: Props) {
/>
</EuiPopover>
{!!slo && isRuleFlyoutVisible ? (
{slo && isRuleFlyoutVisible ? (
<AddRuleFlyout
consumer={sloFeatureId}
ruleTypeId={SLO_BURN_RATE_RULE_TYPE_ID}

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import moment from 'moment';
import React from 'react';
import { SloStatusBadge } from '../../../components/slo/slo_status_badge';
export interface Props {
@ -28,11 +29,40 @@ export function HeaderTitle(props: Props) {
}
return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow={false}>{slo.name}</EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center" responsive={false}>
<SloStatusBadge slo={slo} />
<>
<EuiFlexGroup gutterSize="s">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow={false}>{slo.name}</EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center" responsive={false}>
<SloStatusBadge slo={slo} />
</EuiFlexGroup>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="xs">
<strong>
{i18n.translate('xpack.observability.slo.sloDetails.headerTitle.lastUpdatedMessage', {
defaultMessage: 'Last updated on',
})}
</strong>
&nbsp;
{moment(slo.updatedAt).format('ll')}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="xs">
<strong>
{i18n.translate('xpack.observability.slo.sloDetails.headerTitle.createdMessage', {
defaultMessage: 'Created on',
})}
</strong>
&nbsp;
{moment(slo.createdAt).format('ll')}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
}

View file

@ -1,171 +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 { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import {
occurrencesBudgetingMethodSchema,
rollingTimeWindowTypeSchema,
SLOWithSummaryResponse,
} from '@kbn/slo-schema';
import moment from 'moment';
import React from 'react';
import { useKibana } from '../../../utils/kibana_react';
import {
BUDGETING_METHOD_OCCURRENCES,
BUDGETING_METHOD_TIMESLICES,
toDurationAdverbLabel,
toDurationLabel,
toIndicatorTypeLabel,
} from '../../../utils/slo/labels';
import { OverviewItem } from './overview_item';
export interface Props {
slo: SLOWithSummaryResponse;
}
export function Overview({ slo }: Props) {
const { uiSettings } = useKibana().services;
const dateFormat = uiSettings.get('dateFormat');
const percentFormat = uiSettings.get('format:percent:defaultPattern');
const hasNoData = slo.summary.status === 'NO_DATA';
return (
<EuiPanel paddingSize="none" color="transparent" data-test-subj="overview">
<EuiFlexGroup direction="column" gutterSize="l">
<EuiFlexGroup direction="row" alignItems="flexStart">
<OverviewItem
title={i18n.translate(
'xpack.observability.slo.sloDetails.overview.observedValueTitle',
{
defaultMessage: 'Observed value',
}
)}
subtitle={
<EuiText size="s">
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.observedValueSubtitle',
{
defaultMessage: '{value} (objective is {objective})',
values: {
value: hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat),
objective: numeral(slo.objective.target).format(percentFormat),
},
}
)}
</EuiText>
}
/>
<OverviewItem
title={i18n.translate(
'xpack.observability.slo.sloDetails.overview.indicatorTypeTitle',
{
defaultMessage: 'Indicator type',
}
)}
subtitle={<EuiText size="s">{toIndicatorTypeLabel(slo.indicator.type)}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.timeWindowTitle', {
defaultMessage: 'Time window',
})}
subtitle={toTimeWindowLabel(slo.timeWindow)}
/>
<OverviewItem
title={i18n.translate(
'xpack.observability.slo.sloDetails.overview.budgetingMethodTitle',
{ defaultMessage: 'Budgeting method' }
)}
subtitle={
occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) ? (
<EuiText size="s">{BUDGETING_METHOD_OCCURRENCES}</EuiText>
) : (
<EuiText size="s">
{BUDGETING_METHOD_TIMESLICES} (
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethodDetails',
{
defaultMessage: '{duration} slices, {target} target',
values: {
duration: toDurationLabel(slo.objective.timesliceWindow!),
target: numeral(slo.objective.timesliceTarget!).format(percentFormat),
},
}
)}
)
</EuiText>
)
}
/>
</EuiFlexGroup>
<EuiFlexGroup direction="row" alignItems="flexStart">
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.descriptionTitle', {
defaultMessage: 'Description',
})}
subtitle={<EuiText size="s">{!!slo.description ? slo.description : '-'}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.createdAtTitle', {
defaultMessage: 'Created at',
})}
subtitle={<EuiText size="s">{moment(slo.createdAt).format(dateFormat)}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.updatedAtTitle', {
defaultMessage: 'Last update at',
})}
subtitle={<EuiText size="s">{moment(slo.updatedAt).format(dateFormat)}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.tagsTitle', {
defaultMessage: 'Tags',
})}
subtitle={
slo.tags.length > 0 ? (
<EuiFlexGroup
direction="row"
alignItems="flexStart"
gutterSize="s"
responsive={false}
>
{slo.tags.map((tag) => (
<EuiFlexItem grow={false} key={tag}>
<EuiBadge color="hollow">{tag}</EuiBadge>
</EuiFlexItem>
))}
</EuiFlexGroup>
) : (
<EuiText size="s">-</EuiText>
)
}
/>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiPanel>
);
}
function toTimeWindowLabel(timeWindow: SLOWithSummaryResponse['timeWindow']): string {
if (rollingTimeWindowTypeSchema.is(timeWindow.type)) {
return i18n.translate('xpack.observability.slo.sloDetails.overview.rollingTimeWindow', {
defaultMessage: '{duration} rolling',
values: {
duration: toDurationLabel(timeWindow.duration),
},
});
}
return i18n.translate('xpack.observability.slo.sloDetails.overview.calendarAlignedTimeWindow', {
defaultMessage: '{duration} calendar aligned',
values: {
duration: toDurationAdverbLabel(timeWindow.duration),
},
});
}

View file

@ -0,0 +1,85 @@
/*
* 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 { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { APMTransactionDurationIndicator, APMTransactionErrorRateIndicator } from '@kbn/slo-schema';
import React from 'react';
import { useKibana } from '../../../../utils/kibana_react';
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
import { OverviewItem } from './overview_item';
interface Props {
indicator: APMTransactionDurationIndicator | APMTransactionErrorRateIndicator;
}
export function ApmIndicatorOverview({ indicator }: Props) {
const {
http: { basePath },
} = useKibana().services;
const { service, transactionType, transactionName, environment, filter } = indicator.params;
const link = basePath.prepend(
convertSliApmParamsToApmAppDeeplinkUrl({
environment,
filter,
service,
transactionName,
transactionType,
})
);
return (
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.apmSource', {
defaultMessage: 'APM source',
})}
subtitle={
<EuiFlexGroup direction="row" alignItems="flexStart" gutterSize="s" responsive={false} wrap>
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" href={link}>
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.apmSource.serviceLabel',
{ defaultMessage: 'service: {value}', values: { value: service } }
)}
</EuiBadge>
</EuiFlexItem>
{environment !== '*' && (
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" href={link}>
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.apmSource.environmentLabel',
{ defaultMessage: 'environment: {value}', values: { value: environment } }
)}
</EuiBadge>
</EuiFlexItem>
)}
{transactionType !== '*' && (
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" href={link}>
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.apmSource.transactionTypeLabel',
{ defaultMessage: 'transactionType: {value}', values: { value: transactionType } }
)}
</EuiBadge>
</EuiFlexItem>
)}
{transactionName !== '*' && (
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" href={link}>
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.apmSource.transactionNameLabel',
{ defaultMessage: 'transactionName: {value}', values: { value: transactionName } }
)}
</EuiBadge>
</EuiFlexItem>
)}
</EuiFlexGroup>
}
/>
);
}

View file

@ -8,8 +8,8 @@
import React from 'react';
import { ComponentStory } from '@storybook/react';
import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator';
import { buildSlo } from '../../../data/slo/slo';
import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator';
import { buildSlo } from '../../../../data/slo/slo';
import { Overview as Component, Props } from './overview';
export default {

View file

@ -0,0 +1,164 @@
/*
* 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 {
EuiBadge,
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiText,
useIsWithinBreakpoints,
} from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import {
occurrencesBudgetingMethodSchema,
rollingTimeWindowTypeSchema,
SLOWithSummaryResponse,
} from '@kbn/slo-schema';
import React from 'react';
import { useKibana } from '../../../../utils/kibana_react';
import {
BUDGETING_METHOD_OCCURRENCES,
BUDGETING_METHOD_TIMESLICES,
toDurationAdverbLabel,
toDurationLabel,
toIndicatorTypeLabel,
} from '../../../../utils/slo/labels';
import { ApmIndicatorOverview } from './apm_indicator_overview';
import { OverviewItem } from './overview_item';
export interface Props {
slo: SLOWithSummaryResponse;
}
export function Overview({ slo }: Props) {
const isMobile = useIsWithinBreakpoints(['xs', 's']);
const { uiSettings } = useKibana().services;
const percentFormat = uiSettings.get('format:percent:defaultPattern');
const hasNoData = slo.summary.status === 'NO_DATA';
let IndicatorOverview = null;
switch (slo.indicator.type) {
case 'sli.apm.transactionDuration':
case 'sli.apm.transactionErrorRate':
IndicatorOverview = <ApmIndicatorOverview indicator={slo.indicator} />;
break;
}
return (
<EuiPanel paddingSize="none" color="transparent" data-test-subj="overview">
<EuiFlexGrid columns={isMobile ? 2 : 4} gutterSize="l" responsive={false}>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.observedValueTitle', {
defaultMessage: 'Observed value',
})}
subtitle={
<EuiText size="s">
{i18n.translate('xpack.observability.slo.sloDetails.overview.observedValueSubtitle', {
defaultMessage: '{value} (objective is {objective})',
values: {
value: hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat),
objective: numeral(slo.objective.target).format(percentFormat),
},
})}
</EuiText>
}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.indicatorTypeTitle', {
defaultMessage: 'Indicator type',
})}
subtitle={<EuiText size="s">{toIndicatorTypeLabel(slo.indicator.type)}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.timeWindowTitle', {
defaultMessage: 'Time window',
})}
subtitle={toTimeWindowLabel(slo.timeWindow)}
/>
<OverviewItem
title={i18n.translate(
'xpack.observability.slo.sloDetails.overview.budgetingMethodTitle',
{ defaultMessage: 'Budgeting method' }
)}
subtitle={
occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) ? (
<EuiText size="s">{BUDGETING_METHOD_OCCURRENCES}</EuiText>
) : (
<EuiText size="s">
{BUDGETING_METHOD_TIMESLICES} (
{i18n.translate(
'xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethodDetails',
{
defaultMessage: '{duration} slices, {target} target',
values: {
duration: toDurationLabel(slo.objective.timesliceWindow!),
target: numeral(slo.objective.timesliceTarget!).format(percentFormat),
},
}
)}
)
</EuiText>
)
}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.descriptionTitle', {
defaultMessage: 'Description',
})}
subtitle={<EuiText size="s">{!!slo.description ? slo.description : '-'}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.observability.slo.sloDetails.overview.tagsTitle', {
defaultMessage: 'Tags',
})}
subtitle={
slo.tags.length > 0 ? (
<EuiFlexGroup
direction="row"
alignItems="flexStart"
gutterSize="s"
responsive={false}
wrap
>
{slo.tags.map((tag) => (
<EuiFlexItem grow={false} key={tag}>
<EuiBadge color="hollow">{tag}</EuiBadge>
</EuiFlexItem>
))}
</EuiFlexGroup>
) : (
<EuiText size="s">-</EuiText>
)
}
/>
{IndicatorOverview}
</EuiFlexGrid>
</EuiPanel>
);
}
function toTimeWindowLabel(timeWindow: SLOWithSummaryResponse['timeWindow']): string {
if (rollingTimeWindowTypeSchema.is(timeWindow.type)) {
return i18n.translate('xpack.observability.slo.sloDetails.overview.rollingTimeWindow', {
defaultMessage: '{duration} rolling',
values: {
duration: toDurationLabel(timeWindow.duration),
},
});
}
return i18n.translate('xpack.observability.slo.sloDetails.overview.calendarAlignedTimeWindow', {
defaultMessage: '{duration} calendar aligned',
values: {
duration: toDurationAdverbLabel(timeWindow.duration),
},
});
}

View file

@ -15,15 +15,13 @@ export interface Props {
export function OverviewItem({ title, subtitle }: Props) {
return (
<EuiFlexItem grow={true}>
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem>
<EuiText size="xs">
<strong>{title}</strong>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>{subtitle}</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiText size="xs">
<strong>{title}</strong>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>{subtitle}</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -21,7 +21,7 @@ import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts
import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary';
import { ErrorBudgetChartPanel } from './error_budget_chart_panel';
import { Overview as Overview } from './overview';
import { Overview } from './overview/overview';
import { SliChartPanel } from './sli_chart_panel';
import { SloDetailsAlerts } from './slo_detail_alerts';
import { BurnRates } from './burn_rates';

View file

@ -6,7 +6,7 @@
*/
interface Props {
duration: string;
duration?: string;
environment: string;
filter: string | undefined;
service: string;

View file

@ -26771,13 +26771,11 @@
"xpack.observability.slo.sloDetails.headerControl.edit": "Modifier",
"xpack.observability.slo.sloDetails.headerControl.manageRules": "Gérer les règles",
"xpack.observability.slo.sloDetails.overview.budgetingMethodTitle": "Méthode de budgétisation",
"xpack.observability.slo.sloDetails.overview.createdAtTitle": "Créé à",
"xpack.observability.slo.sloDetails.overview.descriptionTitle": "Description",
"xpack.observability.slo.sloDetails.overview.indicatorTypeTitle": "Type dindicateur",
"xpack.observability.slo.sloDetails.overview.observedValueTitle": "Valeur observée",
"xpack.observability.slo.sloDetails.overview.tagsTitle": "Balises",
"xpack.observability.slo.sloDetails.overview.timeWindowTitle": "Fenêtre temporelle",
"xpack.observability.slo.sloDetails.overview.updatedAtTitle": "Dernière mise à jour à",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.chartTitle": "Valeur SLI",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.current": "Valeur observée",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.objective": "Objectif",

View file

@ -26757,13 +26757,11 @@
"xpack.observability.slo.sloDetails.headerControl.edit": "編集",
"xpack.observability.slo.sloDetails.headerControl.manageRules": "ルールの管理",
"xpack.observability.slo.sloDetails.overview.budgetingMethodTitle": "予算設定方法",
"xpack.observability.slo.sloDetails.overview.createdAtTitle": "作成日時:",
"xpack.observability.slo.sloDetails.overview.descriptionTitle": "説明",
"xpack.observability.slo.sloDetails.overview.indicatorTypeTitle": "インジケータータイプ",
"xpack.observability.slo.sloDetails.overview.observedValueTitle": "観測された値",
"xpack.observability.slo.sloDetails.overview.tagsTitle": "タグ",
"xpack.observability.slo.sloDetails.overview.timeWindowTitle": "時間枠",
"xpack.observability.slo.sloDetails.overview.updatedAtTitle": "最終更新日時",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.chartTitle": "SLI値",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.current": "観測された値",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.objective": "目的",

View file

@ -26755,13 +26755,11 @@
"xpack.observability.slo.sloDetails.headerControl.edit": "编辑",
"xpack.observability.slo.sloDetails.headerControl.manageRules": "管理规则",
"xpack.observability.slo.sloDetails.overview.budgetingMethodTitle": "预算编制方法",
"xpack.observability.slo.sloDetails.overview.createdAtTitle": "创建于",
"xpack.observability.slo.sloDetails.overview.descriptionTitle": "描述",
"xpack.observability.slo.sloDetails.overview.indicatorTypeTitle": "指标类型",
"xpack.observability.slo.sloDetails.overview.observedValueTitle": "观察值",
"xpack.observability.slo.sloDetails.overview.tagsTitle": "标签",
"xpack.observability.slo.sloDetails.overview.timeWindowTitle": "时间窗口",
"xpack.observability.slo.sloDetails.overview.updatedAtTitle": "上次更新时间",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.chartTitle": "SLI 值",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.current": "观察值",
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.objective": "目标",