[Obs-UX-Mgmt] Split Up SLO Details from Overview (#212826)

## Summary 

In an effort to make SLI charts more quickly visible on the SLO overview
page, remove SLO details that do not give users valuable insight into
key metrics and add them to a new tab. Retain some of the SLO details
above the tabs like SLI value, tags, and description (see figma for the
inspiration)


https://www.figma.com/design/91R0OtRZHy5xvaE8dGStBo/SLO%2FSLI-assets?node-id=4601-59103&t=K1vI6qtXbb48XPgr-1

<img width="1474" alt="Screenshot 2025-02-28 at 4 53 05 PM"
src="https://github.com/user-attachments/assets/3fdbe766-4047-45b5-a986-3a029c09bd1f"
/>

![Screenshot 2025-03-06 at 9 54
37 AM](https://github.com/user-attachments/assets/c3ab0bc7-5187-42d4-bf29-48865dd759e2)


## Release Notes

SLO overview should give users a clear, immediate picture into key
objective data. Previously, the user would have had to scroll past
static data that describes the SLO definition before seeing valuable
information about their SLIs. This static data has been moved to a
separate tab, making charts more easily accessible.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Bailey Cash 2025-03-06 12:43:57 -05:00 committed by GitHub
parent 65a41a6b96
commit 34fa3135a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 203 additions and 137 deletions

View file

@ -40274,19 +40274,15 @@
"xpack.slo.sloDetails.overview.apmSource.transactionTypeLabel": "type de transaction : {value}",
"xpack.slo.sloDetails.overview.budgetingMethodTitle": "Méthode de budgétisation",
"xpack.slo.sloDetails.overview.calendarAlignedTimeWindow": "calendrier {duration} aligné",
"xpack.slo.sloDetails.overview.descriptionTitle": "Description",
"xpack.slo.sloDetails.overview.goodQueryTitle": "Bonne question",
"xpack.slo.sloDetails.overview.indexTitle": "Modèle d'indexation",
"xpack.slo.sloDetails.overview.indicatorTypeTitle": "Type dindicateur",
"xpack.slo.sloDetails.overview.observedValueSubtitle": "{value} (l'objectif est {objective})",
"xpack.slo.sloDetails.overview.observedValueTitle": "Valeur observée",
"xpack.slo.sloDetails.overview.overallQueryTitle": "Requête générale",
"xpack.slo.sloDetails.overview.rollingTimeWindow": "{duration} en cours",
"xpack.slo.sloDetails.overview.syntheticsMonitor": "Moniteur de Synthetics",
"xpack.slo.sloDetails.overview.syntheticsMonitor.locationName": "Emplacement : {value}",
"xpack.slo.sloDetails.overview.syntheticsMonitor.name": "Nom : {value}",
"xpack.slo.sloDetails.overview.syntheticsMonitorDetails": "Détails du moniteur Synthetics",
"xpack.slo.sloDetails.overview.tagsTitle": "Balises",
"xpack.slo.sloDetails.overview.timeslicesBudgetingMethodDetails": "{duration} sections, {target} cible",
"xpack.slo.sloDetails.overview.timeslicesBudgetingMethodDetailsForTimesliceMetric": "{duration} sections",
"xpack.slo.sloDetails.overview.timeWindowTitle": "Fenêtre temporelle",

View file

@ -40132,19 +40132,15 @@
"xpack.slo.sloDetails.overview.apmSource.transactionTypeLabel": "transactionType: {value}",
"xpack.slo.sloDetails.overview.budgetingMethodTitle": "バジェット計算方法",
"xpack.slo.sloDetails.overview.calendarAlignedTimeWindow": "{duration}カレンダーが調整されました",
"xpack.slo.sloDetails.overview.descriptionTitle": "説明",
"xpack.slo.sloDetails.overview.goodQueryTitle": "正常系クエリー",
"xpack.slo.sloDetails.overview.indexTitle": "インデックスパターン",
"xpack.slo.sloDetails.overview.indicatorTypeTitle": "インジケータータイプ",
"xpack.slo.sloDetails.overview.observedValueSubtitle": "{value}(目標は{objective}です)",
"xpack.slo.sloDetails.overview.observedValueTitle": "観測された値",
"xpack.slo.sloDetails.overview.overallQueryTitle": "全体的なクエリ",
"xpack.slo.sloDetails.overview.rollingTimeWindow": "{duration}ローリング",
"xpack.slo.sloDetails.overview.syntheticsMonitor": "Syntheticsモニター",
"xpack.slo.sloDetails.overview.syntheticsMonitor.locationName": "場所:{value}",
"xpack.slo.sloDetails.overview.syntheticsMonitor.name": "名前:{value}",
"xpack.slo.sloDetails.overview.syntheticsMonitorDetails": "Syntheticsモニター詳細",
"xpack.slo.sloDetails.overview.tagsTitle": "タグ",
"xpack.slo.sloDetails.overview.timeslicesBudgetingMethodDetails": "{duration}スライス、{target}目標",
"xpack.slo.sloDetails.overview.timeslicesBudgetingMethodDetailsForTimesliceMetric": "{duration}スライス",
"xpack.slo.sloDetails.overview.timeWindowTitle": "時間枠",

View file

@ -39556,19 +39556,15 @@
"xpack.slo.sloDetails.overview.apmSource.transactionTypeLabel": "事务类型:{value}",
"xpack.slo.sloDetails.overview.budgetingMethodTitle": "预算编制方法",
"xpack.slo.sloDetails.overview.calendarAlignedTimeWindow": "{duration} 日历已对齐",
"xpack.slo.sloDetails.overview.descriptionTitle": "描述",
"xpack.slo.sloDetails.overview.goodQueryTitle": "良好查询",
"xpack.slo.sloDetails.overview.indexTitle": "索引模式",
"xpack.slo.sloDetails.overview.indicatorTypeTitle": "指标类型",
"xpack.slo.sloDetails.overview.observedValueSubtitle": "{value}(目标为 {objective}",
"xpack.slo.sloDetails.overview.observedValueTitle": "观察值",
"xpack.slo.sloDetails.overview.overallQueryTitle": "总体查询",
"xpack.slo.sloDetails.overview.rollingTimeWindow": "{duration} 滚动",
"xpack.slo.sloDetails.overview.syntheticsMonitor": "Synthetics 监测",
"xpack.slo.sloDetails.overview.syntheticsMonitor.locationName": "位置:{value}",
"xpack.slo.sloDetails.overview.syntheticsMonitor.name": "名称:{value}",
"xpack.slo.sloDetails.overview.syntheticsMonitorDetails": "Synthetics 监测详情",
"xpack.slo.sloDetails.overview.tagsTitle": "标签",
"xpack.slo.sloDetails.overview.timeslicesBudgetingMethodDetails": "{duration} 切片,{target} 目标",
"xpack.slo.sloDetails.overview.timeslicesBudgetingMethodDetailsForTimesliceMetric": "{duration} 切片",
"xpack.slo.sloDetails.overview.timeWindowTitle": "时间窗口",

View file

@ -8,3 +8,4 @@
export { SloStatusBadge } from './slo_status_badge';
export { SloActiveAlertsBadge } from './slo_active_alerts_badge';
export { SloStateBadge } from './slo_state_badge';
export { SloValueBadge } from './slo_value_badge';

View file

@ -6,58 +6,72 @@
*/
import React from 'react';
import { EuiBadge, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import { EuiBadge, EuiFlexItem, EuiSkeletonText, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
export interface SloStatusProps {
slo: SLOWithSummaryResponse;
isLoading?: boolean;
}
export function SloStatusBadge({ slo }: SloStatusProps) {
interface StatusHealth {
displayText: string;
badgeColor: string;
}
type SLOStatus = 'HEALTHY' | 'DEGRADING' | 'VIOLATED' | 'NO_DATA';
export const displayStatus: Record<SLOStatus, StatusHealth> = {
HEALTHY: {
displayText: i18n.translate('xpack.slo.sloStatusBadge.healthy', {
defaultMessage: 'Healthy',
}),
badgeColor: 'success',
},
DEGRADING: {
displayText: i18n.translate('xpack.slo.sloStatusBadge.degrading', {
defaultMessage: 'Degrading',
}),
badgeColor: 'warning',
},
VIOLATED: {
displayText: i18n.translate('xpack.slo.sloStatusBadge.violated', {
defaultMessage: 'Violated',
}),
badgeColor: 'danger',
},
NO_DATA: {
displayText: i18n.translate('xpack.slo.sloStatusBadge.noData', {
defaultMessage: 'No Data',
}),
badgeColor: 'default',
},
};
export function SloStatusBadge({ slo, isLoading }: SloStatusProps) {
if (isLoading || !slo) {
return <EuiSkeletonText lines={2} data-test-subj="loadingTitle" />;
}
return (
<>
<EuiFlexItem grow={false}>
{slo.summary.status === 'NO_DATA' && (
{slo.summary.status === 'NO_DATA' ? (
<EuiToolTip
position="top"
content={i18n.translate('xpack.slo.sloStatusBadge.noDataTooltip', {
defaultMessage: 'It may take some time before the data is aggregated and available.',
})}
>
<EuiBadge color="default">
{i18n.translate('xpack.slo.sloStatusBadge.noData', {
defaultMessage: 'No data',
})}
<EuiBadge color={displayStatus[slo.summary.status].badgeColor}>
{displayStatus[slo.summary.status]?.displayText}
</EuiBadge>
</EuiToolTip>
)}
{slo.summary.status === 'HEALTHY' && (
<div>
<EuiBadge color="success">
{i18n.translate('xpack.slo.sloStatusBadge.healthy', {
defaultMessage: 'Healthy',
})}
</EuiBadge>
</div>
)}
{slo.summary.status === 'DEGRADING' && (
<div>
<EuiBadge color="warning">
{i18n.translate('xpack.slo.sloStatusBadge.degrading', {
defaultMessage: 'Degrading',
})}
</EuiBadge>
</div>
)}
{slo.summary.status === 'VIOLATED' && (
<div>
<EuiBadge color="danger">
{i18n.translate('xpack.slo.sloStatusBadge.violated', {
defaultMessage: 'Violated',
})}
</EuiBadge>
</div>
) : (
<EuiBadge color={displayStatus[slo.summary.status].badgeColor}>
{displayStatus[slo.summary.status]?.displayText}
</EuiBadge>
)}
</EuiFlexItem>

View file

@ -0,0 +1,61 @@
/*
* 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, { useMemo } from 'react';
import { EuiBadge, EuiFlexItem, EuiSkeletonText, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import numeral from '@elastic/numeral';
import { useKibana } from '../../../hooks/use_kibana';
import { displayStatus } from './slo_status_badge';
export interface SloStatusProps {
slo: SLOWithSummaryResponse;
isLoading?: boolean;
}
export function SloValueBadge({ slo, isLoading }: SloStatusProps) {
const hasNoData = slo?.summary.status === 'NO_DATA';
const { uiSettings } = useKibana().services;
const percentFormat = uiSettings.get('format:percent:defaultPattern');
const badgeDisplayText = useMemo(() => {
return i18n.translate('xpack.slo.sloStatusBadge.sloObjectiveValue', {
defaultMessage: '{value} / {objective} (Objective)',
values: {
value: hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat),
objective: numeral(slo.objective.target).format(percentFormat),
},
});
}, [slo, percentFormat, hasNoData]);
if (isLoading || !slo) {
return <EuiSkeletonText lines={2} data-test-subj="loadingTitle" />;
}
return (
<>
<EuiFlexItem grow={false}>
{slo.summary.status === 'NO_DATA' ? (
<EuiToolTip
position="top"
content={i18n.translate('xpack.slo.sloStatusBadge.noDataTooltip', {
defaultMessage: 'It may take some time before the data is aggregated and available.',
})}
>
<EuiBadge color={displayStatus[slo.summary.status].badgeColor}>
{badgeDisplayText}
</EuiBadge>
</EuiToolTip>
) : (
<EuiBadge color={displayStatus[slo.summary.status].badgeColor}>
{badgeDisplayText}
</EuiBadge>
)}
</EuiFlexItem>
</>
);
}

View file

@ -5,12 +5,19 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, EuiMarkdownFormat, EuiSkeletonText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
EuiFlexItem,
EuiMarkdownFormat,
EuiSkeletonText,
EuiText,
} from '@elastic/eui';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import moment from 'moment';
import React from 'react';
import { SloStateBadge, SloStatusBadge } from '../../../components/slo/slo_badges';
import { TagsList } from '@kbn/observability-shared-plugin/public';
import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { SloStateBadge, SloStatusBadge, SloValueBadge } from '../../../components/slo/slo_badges';
import { SloRemoteBadge } from '../../slos/components/badges/slo_remote_badge';
import { SLOGroupings } from './groupings/slo_groupings';
@ -34,41 +41,36 @@ export function HeaderTitle({ isLoading, slo }: Props) {
responsive={false}
wrap={true}
>
<SloStatusBadge slo={slo} />
<SloValueBadge slo={slo} isLoading={isLoading} />
<SloStatusBadge slo={slo} isLoading={isLoading} />
<SloStateBadge slo={slo} />
<SloRemoteBadge slo={slo} />
<EuiFlexGroup
direction="row"
gutterSize="m"
alignItems="center"
justifyContent="flexStart"
responsive={false}
wrap={true}
>
<EuiFlexItem grow={false}>
<EuiMarkdownFormat textSize="xs" color="subdued">
{i18n.translate('xpack.slo.sloDetails.headerTitle.lastUpdatedLabel', {
defaultMessage: '**Last updated by** {updatedBy} **on** {updatedAt}',
values: {
updatedBy: slo.updatedBy ?? NOT_AVAILABLE_LABEL,
updatedAt: moment(slo.updatedAt).format('ll'),
},
})}
</EuiMarkdownFormat>
{!!slo?.tags?.length && (
<EuiFlexItem grow={true}>
<TagsList tags={slo.tags} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiMarkdownFormat textSize="xs" color="subdued">
{i18n.translate('xpack.slo.sloDetails.headerTitle.createdLabel', {
defaultMessage: '**Created by** {createdBy} **on** {createdAt}',
values: {
createdBy: slo.createdBy ?? NOT_AVAILABLE_LABEL,
createdAt: moment(slo.createdAt).format('ll'),
},
})}
</EuiMarkdownFormat>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiFlexGroup>
{slo.description && (
<EuiFlexItem grow={true}>
<EuiText className={'eui-textBreakWord'}>
<EuiMarkdownFormat textSize="xs" color="subdued">
{slo.description}
</EuiMarkdownFormat>
</EuiText>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiMarkdownFormat textSize="xs" color="subdued">
{i18n.translate('xpack.slo.sloDetails.headerTitle.lastUpdatedLabel', {
defaultMessage: '**Last updated by** {updatedBy} **on** {updatedAt}',
values: {
updatedBy: slo.updatedBy ?? NOT_AVAILABLE_LABEL,
updatedAt: moment(slo.updatedAt).format('ll'),
},
})}
</EuiMarkdownFormat>
</EuiFlexItem>
<SLOGroupings slo={slo} />
</EuiFlexGroup>
);

View file

@ -16,7 +16,7 @@ import {
import React from 'react';
import { useKibana } from '../../../../hooks/use_kibana';
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
import { OverviewItem } from './overview_item';
import { DefinitionItem } from './definition_item';
interface Props {
slo: SLOWithSummaryResponse;
@ -45,7 +45,7 @@ export function ApmIndicatorOverview({ slo }: Props) {
} = indicator;
return (
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.apmSource', {
defaultMessage: 'APM source',
})}

View file

@ -8,7 +8,6 @@
import { EuiFlexGrid, EuiPanel, EuiText, useIsWithinBreakpoints } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import { TagsList } from '@kbn/observability-shared-plugin/public';
import {
SLOWithSummaryResponse,
occurrencesBudgetingMethodSchema,
@ -26,18 +25,17 @@ import {
} from '../../../../utils/slo/labels';
import { ApmIndicatorOverview } from './apm_indicator_overview';
import { DisplayQuery } from './display_query';
import { OverviewItem } from './overview_item';
import { DefinitionItem } from './definition_item';
import { SyntheticsIndicatorOverview } from './synthetics_indicator_overview';
export interface Props {
slo: SLOWithSummaryResponse;
}
export function Overview({ slo }: Props) {
export function Definition({ 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) {
@ -50,37 +48,21 @@ export function Overview({ slo }: Props) {
}
return (
<EuiPanel paddingSize="none" color="transparent" data-test-subj="overview">
<EuiPanel paddingSize="none" color="transparent" data-test-subj="definition">
<EuiFlexGrid columns={isMobile ? 2 : 4} gutterSize="l" responsive={false}>
<OverviewItem
title={i18n.translate('xpack.slo.sloDetails.overview.observedValueTitle', {
defaultMessage: 'Observed value',
})}
subtitle={
<EuiText size="s">
{i18n.translate('xpack.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
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.indicatorTypeTitle', {
defaultMessage: 'Indicator type',
})}
subtitle={<EuiText size="s">{toIndicatorTypeLabel(slo.indicator.type)}</EuiText>}
/>
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.timeWindowTitle', {
defaultMessage: 'Time window',
})}
subtitle={toTimeWindowLabel(slo.timeWindow)}
/>
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.budgetingMethodTitle', {
defaultMessage: 'Budgeting method',
})}
@ -115,21 +97,10 @@ export function Overview({ slo }: Props) {
)
}
/>
<OverviewItem
title={i18n.translate('xpack.slo.sloDetails.overview.descriptionTitle', {
defaultMessage: 'Description',
})}
subtitle={<EuiText size="s">{!!slo.description ? slo.description : '-'}</EuiText>}
/>
<OverviewItem
title={i18n.translate('xpack.slo.sloDetails.overview.tagsTitle', {
defaultMessage: 'Tags',
})}
subtitle={<TagsList tags={slo.tags} />}
/>
{IndicatorOverview}
{'index' in slo.indicator.params && (
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.indexTitle', {
defaultMessage: 'Index pattern',
})}
@ -137,7 +108,7 @@ export function Overview({ slo }: Props) {
/>
)}
{'filter' in slo.indicator.params && (
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.overallQueryTitle', {
defaultMessage: 'Overall query',
})}
@ -150,7 +121,7 @@ export function Overview({ slo }: Props) {
/>
)}
{'good' in slo.indicator.params && querySchema.is(slo.indicator.params.good) && (
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.goodQueryTitle', {
defaultMessage: 'Good query',
})}
@ -160,7 +131,7 @@ export function Overview({ slo }: Props) {
/>
)}
{'total' in slo.indicator.params && querySchema.is(slo.indicator.params.total) && (
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.totalQueryTitle', {
defaultMessage: 'Total query',
})}
@ -170,13 +141,13 @@ export function Overview({ slo }: Props) {
/>
)}
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.settings.syncDelay', {
defaultMessage: 'Sync delay',
})}
subtitle={slo.settings.syncDelay}
/>
<OverviewItem
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.settings.frequency', {
defaultMessage: 'Frequency',
})}

View file

@ -13,7 +13,7 @@ export interface Props {
subtitle: ReactNode;
}
export function OverviewItem({ title, subtitle }: Props) {
export function DefinitionItem({ title, subtitle }: Props) {
return (
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem grow={false}>

View file

@ -10,11 +10,11 @@ import { ComponentStory } from '@storybook/react';
import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator';
import { buildSlo } from '../../../../data/slo/slo';
import { Overview as Component, Props } from './overview';
import { Definition as Component, Props } from './definition';
export default {
component: Component,
title: 'app/SLO/DetailsPage/Overview',
title: 'app/SLO/DetailsPage/Definition',
decorators: [KibanaReactStorybookDecorator],
};

View file

@ -14,7 +14,7 @@ import {
syntheticsMonitorLocationQueryLocatorID,
} from '@kbn/observability-plugin/common';
import { useKibana } from '../../../../hooks/use_kibana';
import { OverviewItem } from './overview_item';
import { DefinitionItem } from './definition_item';
interface Props {
slo: SLOWithSummaryResponse;
@ -41,14 +41,14 @@ export function SyntheticsIndicatorOverview({ slo }: Props) {
const onMonitorClick = () => monitorLocator?.navigate({ configId: monitorId, locationId });
const onLocationClick = () => regionLocator?.navigate({ locationId: location });
const showOverviewItem = name || location;
const showDefinitionItem = name || location;
if (!showOverviewItem) {
if (!showDefinitionItem) {
return null;
}
return (
<OverviewItem
<DefinitionItem
title={MONITOR_LABEL}
subtitle={
<EuiFlexGroup direction="row" alignItems="flexStart" gutterSize="s" responsive={false} wrap>

View file

@ -13,7 +13,7 @@ import { BurnRatePanel } from './burn_rate_panel/burn_rate_panel';
import { EventsChartPanel } from './events_chart_panel/events_chart_panel';
import { HistoricalDataCharts } from './historical_data_charts';
import { SLODetailsHistory } from './history/slo_details_history';
import { Overview } from './overview/overview';
import { Definition } from './overview/definition';
import { SloDetailsAlerts } from './slo_detail_alerts';
import { SloHealthCallout } from './slo_health_callout';
import { SloRemoteCallout } from './slo_remote_callout';
@ -21,9 +21,14 @@ 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 DEFINITION_TAB_ID = 'definition';
export const ALERTS_TAB_ID = 'alerts';
export type SloTabId = typeof OVERVIEW_TAB_ID | typeof ALERTS_TAB_ID | typeof HISTORY_TAB_ID;
export type SloTabId =
| typeof OVERVIEW_TAB_ID
| typeof ALERTS_TAB_ID
| typeof HISTORY_TAB_ID
| typeof DEFINITION_TAB_ID;
export interface Props {
slo: SLOWithSummaryResponse;
@ -57,6 +62,10 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
);
}
if (selectedTabId === DEFINITION_TAB_ID) {
return <Definition slo={slo} />;
}
if (selectedTabId === ALERTS_TAB_ID) {
return <SloDetailsAlerts slo={slo} />;
}
@ -65,7 +74,6 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) {
<EuiFlexGroup direction="column" gutterSize="xl">
<SloRemoteCallout slo={slo} />
<SloHealthCallout slo={slo} />
<Overview slo={slo} />
<EuiFlexGroup direction="column" gutterSize="l">
<BurnRatePanel slo={slo} isAutoRefreshing={isAutoRefreshing} />

View file

@ -15,6 +15,7 @@ import { useKibana } from '../../../hooks/use_kibana';
import {
ALERTS_TAB_ID,
HISTORY_TAB_ID,
DEFINITION_TAB_ID,
OVERVIEW_TAB_ID,
SloTabId,
} from '../components/slo_details';
@ -62,6 +63,28 @@ export const useSloDetailsTabs = ({
: undefined,
}),
},
{
id: DEFINITION_TAB_ID,
label: i18n.translate('xpack.slo.sloDetails.tab.definitionLabel', {
defaultMessage: 'Definition',
}),
'data-test-subj': 'definitionTab',
isSelected: selectedTabId === DEFINITION_TAB_ID,
...(setSelectedTabId
? {
onClick: () => setSelectedTabId(DEFINITION_TAB_ID),
}
: {
href: slo
? `${basePath.get()}${paths.sloDetails(
slo.id,
slo.instanceId,
slo.remote?.remoteName,
DEFINITION_TAB_ID
)}`
: undefined,
}),
},
...(slo?.timeWindow.type === 'rolling'
? [
{

View file

@ -229,7 +229,6 @@ describe('SLO Details Page', () => {
render(<SloDetailsPage />);
expect(screen.queryByTestId('sloDetailsPage')).toBeTruthy();
expect(screen.queryByTestId('overview')).toBeTruthy();
expect(screen.queryByTestId('sliChartPanel')).toBeTruthy();
expect(screen.queryByTestId('errorBudgetChartPanel')).toBeTruthy();
expect(screen.queryAllByTestId('wideChartLoading').length).toBe(2);
@ -244,7 +243,6 @@ describe('SLO Details Page', () => {
render(<SloDetailsPage />);
expect(screen.queryByTestId('sloDetailsPage')).toBeTruthy();
expect(screen.queryByTestId('overview')).toBeTruthy();
expect(screen.queryByTestId('sliChartPanel')).toBeTruthy();
expect(screen.queryByTestId('errorBudgetChartPanel')).toBeTruthy();
expect(screen.queryByTestId('errorRateChart')).toBeTruthy();