Skip to content

Commit

Permalink
chore(slo): UI and tests improvements (#152959)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdelemme authored Mar 15, 2023
1 parent fc9a631 commit 57bbdd6
Show file tree
Hide file tree
Showing 23 changed files with 370 additions and 278 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@
* 2.0.
*/

import {
asDecimal,
asInteger,
asPercent,
asPercentWithTwoDecimals,
asDecimalOrInteger,
} from './formatters';
import { asDecimal, asInteger, asPercent, asDecimalOrInteger } from './formatters';

describe('formatters', () => {
describe('asDecimal', () => {
Expand Down Expand Up @@ -95,33 +89,6 @@ describe('formatters', () => {
});
});

describe('asPercentWithTwoDecimals', () => {
it('formats as integer when number is above 10', () => {
expect(asPercentWithTwoDecimals(3725, 10000, 'n/a')).toEqual('37.25%');
});

it('adds a decimal when value is below 10', () => {
expect(asPercentWithTwoDecimals(0.092, 1)).toEqual('9.20%');
});

it('formats when numerator is 0', () => {
expect(asPercentWithTwoDecimals(0, 1, 'n/a')).toEqual('0%');
});

it('returns fallback when denominator is undefined', () => {
expect(asPercentWithTwoDecimals(3725, undefined, 'n/a')).toEqual('n/a');
});

it('returns fallback when denominator is 0 ', () => {
expect(asPercentWithTwoDecimals(3725, 0, 'n/a')).toEqual('n/a');
});

it('returns fallback when numerator or denominator is NaN', () => {
expect(asPercentWithTwoDecimals(3725, NaN, 'n/a')).toEqual('n/a');
expect(asPercentWithTwoDecimals(NaN, 10000, 'n/a')).toEqual('n/a');
});
});

describe('asDecimalOrInteger', () => {
it('formats as integer when number equals to 0 ', () => {
expect(asDecimalOrInteger(0)).toEqual('0');
Expand Down
21 changes: 0 additions & 21 deletions x-pack/plugins/observability/common/utils/formatters/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,6 @@ export function asPercent(
return numeral(decimal).format('0.0%');
}

export function asPercentWithTwoDecimals(
numerator: Maybe<number>,
denominator: number | undefined,
fallbackResult = NOT_AVAILABLE_LABEL
) {
if (!denominator || !isFiniteNumber(numerator)) {
return fallbackResult;
}

const decimal = numerator / denominator;

// 33.2 => 33.20%
// 3.32 => 3.32%
// 0 => 0%
if (String(Math.abs(decimal)).split('.').at(1)?.length === 2 || decimal === 0 || decimal === 1) {
return numeral(decimal).format('0%');
}

return numeral(decimal).format('0.00%');
}

export type AsPercent = typeof asPercent;

export function asDecimalOrInteger(value: number) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
*/

import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTitle } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import React from 'react';

import { useKibana } from '../../../utils/kibana_react';
import { toDurationLabel } from '../../../utils/slo/labels';
import { ChartData } from '../../../typings/slo';
import { toHighPrecisionPercentage } from '../helpers/number';
import { WideChart } from './wide_chart';

export interface Props {
Expand All @@ -21,10 +23,13 @@ export interface Props {
}

export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) {
const { uiSettings } = useKibana().services;
const percentFormat = uiSettings.get('format:percent:defaultPattern');

const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED';

return (
<EuiPanel paddingSize="m" color="transparent" hasBorder>
<EuiPanel paddingSize="m" color="transparent" hasBorder data-test-subj="errorBudgetChartPanel">
<EuiFlexGroup direction="column" gutterSize="l">
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
Expand All @@ -40,7 +45,7 @@ export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) {
<EuiText color="subdued" size="s">
{i18n.translate('xpack.observability.slo.sloDetails.errorBudgetChartPanel.duration', {
defaultMessage: 'Last {duration}',
values: { duration: slo.timeWindow.duration },
values: { duration: toDurationLabel(slo.timeWindow.duration) },
})}
</EuiText>
</EuiFlexItem>
Expand All @@ -50,7 +55,7 @@ export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) {
<EuiFlexItem grow={false}>
<EuiStat
titleColor={isSloFailed ? 'danger' : 'success'}
title={`${toHighPrecisionPercentage(slo.summary.errorBudget.remaining)}%`}
title={numeral(slo.summary.errorBudget.remaining).format(percentFormat)}
titleSize="s"
description={i18n.translate(
'xpack.observability.slo.sloDetails.errorBudgetChartPanel.remaining',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
*/

import { EuiFlexGroup, EuiPanel } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import { assertNever } from '@kbn/std';
import moment from 'moment';
import React from 'react';

import { toBudgetingMethodLabel, toIndicatorTypeLabel } from '../../../utils/slo/labels';
import { toDurationLabel } from '../../../utils/slo/labels';
import { useKibana } from '../../../utils/kibana_react';
import { toHighPrecisionPercentage } from '../helpers/number';
import { OverviewItem } from './overview_item';

export interface Props {
Expand All @@ -23,10 +24,11 @@ export interface Props {
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">
<EuiPanel paddingSize="none" color="transparent" data-test-subj="overview">
<EuiFlexGroup direction="column" gutterSize="l">
<EuiFlexGroup direction="row" alignItems="flexStart">
<OverviewItem
Expand All @@ -41,8 +43,8 @@ export function Overview({ slo }: Props) {
{
defaultMessage: '{value} (objective is {objective})',
values: {
value: hasNoData ? '-' : `${toHighPrecisionPercentage(slo.summary.sliValue)}%`,
objective: `${toHighPrecisionPercentage(slo.objective.target)}%`,
value: hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat),
objective: numeral(slo.objective.target).format(percentFormat),
},
}
)}
Expand All @@ -69,7 +71,7 @@ export function Overview({ slo }: Props) {
defaultMessage: 'Budgeting method',
}
)}
subtitle={toBudgetingMethod(slo.budgetingMethod)}
subtitle={toBudgetingMethodLabel(slo.budgetingMethod)}
/>
</EuiFlexGroup>

Expand Down Expand Up @@ -109,7 +111,7 @@ function toTimeWindowLabel(timeWindow: SLOWithSummaryResponse['timeWindow']): st
return i18n.translate('xpack.observability.slo.sloDetails.overview.rollingTimeWindow', {
defaultMessage: '{duration} rolling',
values: {
duration: timeWindow.duration,
duration: toDurationLabel(timeWindow.duration),
},
});
}
Expand All @@ -121,40 +123,3 @@ function toTimeWindowLabel(timeWindow: SLOWithSummaryResponse['timeWindow']): st
},
});
}

function toIndicatorTypeLabel(indicatorType: SLOWithSummaryResponse['indicator']['type']): string {
switch (indicatorType) {
case 'sli.kql.custom':
return i18n.translate('xpack.observability.slo.sloDetails.overview.customKqlIndicator', {
defaultMessage: 'Custom KQL',
});

case 'sli.apm.transactionDuration':
return i18n.translate('xpack.observability.slo.sloDetails.overview.apmLatencyIndicator', {
defaultMessage: 'APM latency',
});

case 'sli.apm.transactionErrorRate':
return i18n.translate(
'xpack.observability.slo.sloDetails.overview.apmAvailabilityIndicator',
{
defaultMessage: 'APM availability',
}
);
default:
assertNever(indicatorType);
}
}

function toBudgetingMethod(budgetingMethod: SLOWithSummaryResponse['budgetingMethod']): string {
if (budgetingMethod === 'occurrences') {
return i18n.translate(
'xpack.observability.slo.sloDetails.overview.occurrencesBudgetingMethod',
{ defaultMessage: 'Occurrences' }
);
}

return i18n.translate('xpack.observability.slo.sloDetails.overview.timeslicesBudgetingMethod', {
defaultMessage: 'Timeslices',
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
*/

import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText, EuiTitle } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import React from 'react';

import { useKibana } from '../../../utils/kibana_react';
import { toDurationLabel } from '../../../utils/slo/labels';
import { ChartData } from '../../../typings/slo';
import { toHighPrecisionPercentage } from '../helpers/number';
import { WideChart } from './wide_chart';

export interface Props {
Expand All @@ -21,11 +23,14 @@ export interface Props {
}

export function SliChartPanel({ data, isLoading, slo }: Props) {
const { uiSettings } = useKibana().services;
const percentFormat = uiSettings.get('format:percent:defaultPattern');

const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED';
const hasNoData = slo.summary.status === 'NO_DATA';

return (
<EuiPanel paddingSize="m" color="transparent" hasBorder>
<EuiPanel paddingSize="m" color="transparent" hasBorder data-test-subj="sliChartPanel">
<EuiFlexGroup direction="column" gutterSize="l">
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
Expand All @@ -41,7 +46,7 @@ export function SliChartPanel({ data, isLoading, slo }: Props) {
<EuiText color="subdued" size="s">
{i18n.translate('xpack.observability.slo.sloDetails.sliHistoryChartPanel.duration', {
defaultMessage: 'Last {duration}',
values: { duration: slo.timeWindow.duration },
values: { duration: toDurationLabel(slo.timeWindow.duration) },
})}
</EuiText>
</EuiFlexItem>
Expand All @@ -51,7 +56,7 @@ export function SliChartPanel({ data, isLoading, slo }: Props) {
<EuiFlexItem grow={false}>
<EuiStat
titleColor={isSloFailed ? 'danger' : 'success'}
title={hasNoData ? '-' : `${toHighPrecisionPercentage(slo.summary.sliValue)}%`}
title={hasNoData ? '-' : numeral(slo.summary.sliValue).format(percentFormat)}
titleSize="s"
description={i18n.translate(
'xpack.observability.slo.sloDetails.sliHistoryChartPanel.current',
Expand All @@ -62,7 +67,7 @@ export function SliChartPanel({ data, isLoading, slo }: Props) {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiStat
title={`${toHighPrecisionPercentage(slo.objective.target)}%`}
title={numeral(slo.objective.target).format(percentFormat)}
titleSize="s"
description={i18n.translate(
'xpack.observability.slo.sloDetails.sliHistoryChartPanel.objective',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import {
} from '@elastic/charts';
import React from 'react';
import { EuiIcon, EuiLoadingChart, useEuiTheme } from '@elastic/eui';
import numeral from '@elastic/numeral';
import moment from 'moment';

import { ChartData } from '../../../typings';
import { useKibana } from '../../../utils/kibana_react';
import { toHighPrecisionPercentage } from '../helpers/number';

type ChartType = 'area' | 'line';
type State = 'success' | 'error';
Expand All @@ -40,12 +40,13 @@ export function WideChart({ chart, data, id, isLoading, state }: Props) {
const baseTheme = charts.theme.useChartsBaseTheme();
const { euiTheme } = useEuiTheme();
const dateFormat = uiSettings.get('dateFormat');
const percentFormat = uiSettings.get('format:percent:defaultPattern');

const color = state === 'error' ? euiTheme.colors.danger : euiTheme.colors.success;
const ChartComponent = chart === 'area' ? AreaSeries : LineSeries;

if (isLoading) {
return <EuiLoadingChart size="m" mono />;
return <EuiLoadingChart size="m" mono data-test-subj="wideChartLoading" />;
}

return (
Expand All @@ -67,7 +68,7 @@ export function WideChart({ chart, data, id, isLoading, state }: Props) {
id="left"
ticks={4}
position={Position.Left}
tickFormat={(d) => `${toHighPrecisionPercentage(d)}%`}
tickFormat={(d) => numeral(d).format(percentFormat)}
/>
<ChartComponent
color={color}
Expand Down

This file was deleted.

Loading

0 comments on commit 57bbdd6

Please sign in to comment.