mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Alert details page] Update source and status bar (#194842)
Closes #187110 Closes #187111 Closes #153834 ## Summary This PR changes how we show source and alert status information, as shown below:  Also, for the APM Latency rule type, we now have a threshold component similar to other rules. For the SLO burn rate rule, the SLO link is added to the source list. |Rule type|Screenshot| |---|---| |APM Latency|| |SLO burn rate|| --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ccd0e17c09
commit
1a54aabd6d
37 changed files with 489 additions and 483 deletions
|
@ -4,5 +4,11 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SettingsSpec } from '@elastic/charts';
|
||||
|
||||
export const DEFAULT_DATE_FORMAT = 'HH:mm:ss';
|
||||
export const CHART_ANNOTATION_RED_COLOR = '#BD271E';
|
||||
export const CHART_SETTINGS: Partial<SettingsSpec> = {
|
||||
showLegend: false,
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
|||
import { Theme } from '@elastic/charts';
|
||||
import { AlertActiveTimeRangeAnnotation, AlertAnnotation } from '@kbn/observability-alert-details';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { DEFAULT_DATE_FORMAT } from './constants';
|
||||
import { CHART_SETTINGS, DEFAULT_DATE_FORMAT } from './constants';
|
||||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { ChartType } from '../../../shared/charts/helper/get_timeseries_color';
|
||||
import * as get_timeseries_color from '../../../shared/charts/helper/get_timeseries_color';
|
||||
|
@ -226,6 +226,7 @@ function FailedTransactionChart({
|
|||
comparisonEnabled={false}
|
||||
customTheme={comparisonChartTheme}
|
||||
timeZone={timeZone}
|
||||
settings={CHART_SETTINGS}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { COMPARATORS } from '@kbn/alerting-comparators';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { formatAlertEvaluationValue } from '@kbn/observability-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { formatAlertEvaluationValue, Threshold } from '@kbn/observability-plugin/public';
|
||||
import { useChartThemes } from '@kbn/observability-shared-plugin/public';
|
||||
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
|
||||
import {
|
||||
ALERT_END,
|
||||
ALERT_EVALUATION_THRESHOLD,
|
||||
|
@ -15,9 +21,6 @@ import {
|
|||
ALERT_RULE_TYPE_ID,
|
||||
ALERT_START,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import {
|
||||
|
@ -36,12 +39,7 @@ import ThroughputChart from './throughput_chart';
|
|||
import { AlertDetailsAppSectionProps } from './types';
|
||||
import { createCallApmApi } from '../../../../services/rest/create_call_apm_api';
|
||||
|
||||
export function AlertDetailsAppSection({
|
||||
rule,
|
||||
alert,
|
||||
timeZone,
|
||||
setAlertSummaryFields,
|
||||
}: AlertDetailsAppSectionProps) {
|
||||
export function AlertDetailsAppSection({ rule, alert, timeZone }: AlertDetailsAppSectionProps) {
|
||||
const { services } = useKibana();
|
||||
createCallApmApi(services as CoreStart);
|
||||
|
||||
|
@ -54,42 +52,25 @@ export function AlertDetailsAppSection({
|
|||
const transactionName = alert.fields[TRANSACTION_NAME];
|
||||
const transactionType = alert.fields[TRANSACTION_TYPE];
|
||||
|
||||
useEffect(() => {
|
||||
const alertSummaryFields = [
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.apm.pages.alertDetails.alertSummary.actualValue"
|
||||
defaultMessage="Actual value"
|
||||
/>
|
||||
),
|
||||
value: formatAlertEvaluationValue(alertRuleTypeId, alertEvaluationValue),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.apm.pages.alertDetails.alertSummary.expectedValue"
|
||||
defaultMessage="Expected value"
|
||||
/>
|
||||
),
|
||||
value: formatAlertEvaluationValue(alertRuleTypeId, alertEvaluationThreshold),
|
||||
},
|
||||
];
|
||||
setAlertSummaryFields(alertSummaryFields);
|
||||
}, [
|
||||
alertRuleTypeId,
|
||||
alertEvaluationValue,
|
||||
alertEvaluationThreshold,
|
||||
environment,
|
||||
serviceName,
|
||||
transactionName,
|
||||
setAlertSummaryFields,
|
||||
]);
|
||||
|
||||
const params = rule.params;
|
||||
const latencyAggregationType = getAggsTypeFromRule(params.aggregationType);
|
||||
const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]);
|
||||
const comparisonChartTheme = getComparisonChartTheme();
|
||||
const chartThemes = useChartThemes();
|
||||
const thresholdComponent =
|
||||
alertEvaluationValue && alertEvaluationThreshold ? (
|
||||
<Threshold
|
||||
chartProps={chartThemes}
|
||||
id="latency-threshold"
|
||||
threshold={[alertEvaluationThreshold]}
|
||||
value={alertEvaluationValue}
|
||||
valueFormatter={(d: number) => String(formatAlertEvaluationValue(alertRuleTypeId, d))}
|
||||
title={i18n.translate('xpack.apm.alertDetails.thresholdTitle', {
|
||||
defaultMessage: 'Threshold breached',
|
||||
})}
|
||||
comparator={COMPARATORS.GREATER_THAN}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
const { from, to } = timeRange;
|
||||
if (!from || !to) {
|
||||
|
@ -138,6 +119,7 @@ export function AlertDetailsAppSection({
|
|||
latencyAggregationType={latencyAggregationType}
|
||||
comparisonEnabled={false}
|
||||
offset={''}
|
||||
threshold={thresholdComponent}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { Theme } from '@elastic/charts';
|
||||
import { RecursivePartial } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, ReactElement } from 'react';
|
||||
import { EuiFlexItem, EuiPanel, EuiFlexGroup, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BoolQuery } from '@kbn/es-query';
|
||||
|
@ -38,7 +38,7 @@ import { LatencyAggregationType } from '../../../../../common/latency_aggregatio
|
|||
import { isLatencyThresholdRuleType } from './helpers';
|
||||
import { ApmDocumentType } from '../../../../../common/document_type';
|
||||
import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_preferred_data_source_and_bucket_size';
|
||||
import { DEFAULT_DATE_FORMAT } from './constants';
|
||||
import { CHART_SETTINGS, DEFAULT_DATE_FORMAT } from './constants';
|
||||
import { TransactionTypeSelect } from './transaction_type_select';
|
||||
import { ViewInAPMButton } from './view_in_apm_button';
|
||||
|
||||
|
@ -61,6 +61,7 @@ function LatencyChart({
|
|||
customAlertEvaluationThreshold,
|
||||
kuery = '',
|
||||
filters,
|
||||
threshold,
|
||||
}: {
|
||||
alert: TopAlert;
|
||||
transactionType: string;
|
||||
|
@ -78,6 +79,7 @@ function LatencyChart({
|
|||
offset: string;
|
||||
timeZone: string;
|
||||
customAlertEvaluationThreshold?: number;
|
||||
threshold?: ReactElement;
|
||||
kuery?: string;
|
||||
filters?: BoolQuery;
|
||||
}) {
|
||||
|
@ -245,18 +247,28 @@ function LatencyChart({
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<TimeseriesChart
|
||||
id="latencyChart"
|
||||
annotations={getLatencyChartAdditionalData()}
|
||||
height={200}
|
||||
comparisonEnabled={comparisonEnabled}
|
||||
offset={offset}
|
||||
fetchStatus={status}
|
||||
customTheme={comparisonChartTheme}
|
||||
timeseries={timeseriesLatency}
|
||||
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<EuiFlexGroup direction="row" gutterSize="m">
|
||||
{!!threshold && (
|
||||
<EuiFlexItem style={{ minWidth: 180 }} grow={1}>
|
||||
{threshold}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={!!threshold ? 5 : undefined}>
|
||||
<TimeseriesChart
|
||||
id="latencyChart"
|
||||
annotations={getLatencyChartAdditionalData()}
|
||||
height={200}
|
||||
comparisonEnabled={comparisonEnabled}
|
||||
offset={offset}
|
||||
fetchStatus={status}
|
||||
customTheme={comparisonChartTheme}
|
||||
timeseries={timeseriesLatency}
|
||||
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
|
||||
timeZone={timeZone}
|
||||
settings={CHART_SETTINGS}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CHART_SETTINGS } from './constants';
|
||||
|
||||
import { ChartType, getTimeSeriesColor } from '../../../shared/charts/helper/get_timeseries_color';
|
||||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
|
@ -190,6 +191,7 @@ function ThroughputChart({
|
|||
timeseries={timeseriesThroughput}
|
||||
yLabelFormat={asExactTransactionRate}
|
||||
timeZone={timeZone}
|
||||
settings={CHART_SETTINGS}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { Rule } from '@kbn/alerting-plugin/common';
|
||||
import type { TopAlert, AlertSummaryField } from '@kbn/observability-plugin/public';
|
||||
import type { TopAlert } from '@kbn/observability-plugin/public';
|
||||
import type { TIME_UNITS } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type {
|
||||
SERVICE_NAME,
|
||||
|
@ -28,5 +28,4 @@ export interface AlertDetailsAppSectionProps {
|
|||
[SERVICE_ENVIRONMENT]: string;
|
||||
}>;
|
||||
timeZone: string;
|
||||
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
XYBrushEvent,
|
||||
XYChartSeriesIdentifier,
|
||||
Tooltip,
|
||||
SettingsSpec,
|
||||
} from '@elastic/charts';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -46,11 +47,13 @@ const END_ZONE_LABEL = i18n.translate('xpack.apm.timeseries.endzone', {
|
|||
defaultMessage:
|
||||
'The selected time range does not include this entire bucket. It might contain partial data.',
|
||||
});
|
||||
|
||||
interface TimeseriesChartProps extends TimeseriesChartWithContextProps {
|
||||
comparisonEnabled: boolean;
|
||||
offset?: string;
|
||||
timeZone: string;
|
||||
annotations?: Array<ReactElement<typeof RectAnnotation | typeof LineAnnotation>>;
|
||||
settings?: Partial<SettingsSpec>;
|
||||
}
|
||||
export function TimeseriesChart({
|
||||
id,
|
||||
|
@ -68,6 +71,7 @@ export function TimeseriesChart({
|
|||
offset,
|
||||
timeZone,
|
||||
annotations,
|
||||
settings,
|
||||
}: TimeseriesChartProps) {
|
||||
const history = useHistory();
|
||||
const { chartRef, updatePointerEvent } = useChartPointerEventContext();
|
||||
|
@ -186,6 +190,7 @@ export function TimeseriesChart({
|
|||
}
|
||||
}}
|
||||
locale={i18n.getLocale()}
|
||||
{...settings}
|
||||
/>
|
||||
<Axis
|
||||
id="x-axis"
|
||||
|
|
|
@ -128,6 +128,7 @@
|
|||
"@kbn/aiops-log-rate-analysis",
|
||||
"@kbn/router-utils",
|
||||
"@kbn/react-hooks",
|
||||
"@kbn/alerting-comparators",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
*/
|
||||
|
||||
import { Rule } from '@kbn/alerting-plugin/common';
|
||||
import { AlertSummaryField, TopAlert } from '@kbn/observability-plugin/public';
|
||||
import { TopAlert } from '@kbn/observability-plugin/public';
|
||||
import { PartialRuleParams } from '../../../../../common/alerting/logs/log_threshold';
|
||||
|
||||
export interface AlertDetailsAppSectionProps {
|
||||
rule: Rule<PartialRuleParams>;
|
||||
alert: TopAlert<Record<string, any>>;
|
||||
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ jest.mock('../../../hooks/use_kibana', () => ({
|
|||
|
||||
describe('AlertDetailsAppSection', () => {
|
||||
const queryClient = new QueryClient();
|
||||
const mockedSetAlertSummaryFields = jest.fn();
|
||||
const renderComponent = () => {
|
||||
return render(
|
||||
<IntlProvider locale="en">
|
||||
|
@ -77,7 +76,6 @@ describe('AlertDetailsAppSection', () => {
|
|||
<AlertDetailsAppSection
|
||||
alert={buildMetricThresholdAlert()}
|
||||
rule={buildMetricThresholdRule()}
|
||||
setAlertSummaryFields={mockedSetAlertSummaryFields}
|
||||
/>
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import chroma from 'chroma-js';
|
||||
|
||||
import { AlertSummaryField, RuleConditionChart, TopAlert } from '@kbn/observability-plugin/public';
|
||||
import { RuleConditionChart, TopAlert } from '@kbn/observability-plugin/public';
|
||||
import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES, ALERT_GROUP } from '@kbn/rule-data-utils';
|
||||
import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common';
|
||||
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
|
||||
|
@ -57,10 +57,9 @@ export type MetricThresholdAlert = TopAlert<MetricThresholdAlertField>;
|
|||
interface AppSectionProps {
|
||||
alert: MetricThresholdAlert;
|
||||
rule: MetricThresholdRule;
|
||||
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
|
||||
}
|
||||
|
||||
export function AlertDetailsAppSection({ alert, rule, setAlertSummaryFields }: AppSectionProps) {
|
||||
export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
|
||||
const { charts } = useKibanaContextForPlugin().services;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const groups = alert.fields[ALERT_GROUP];
|
||||
|
|
|
@ -8,27 +8,33 @@
|
|||
import { ALERT_GROUP_FIELD, ALERT_GROUP_VALUE, ALERT_GROUP } from '@kbn/rule-data-utils';
|
||||
import { TopAlert } from '../../typings/alerts';
|
||||
import { apmSources, infraSources } from './get_alert_source_links';
|
||||
import { Group } from '../../../common/typings';
|
||||
|
||||
interface AlertFields {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const getSources = (alert: TopAlert) => {
|
||||
const isGroup = (item: Group | undefined): item is Group => {
|
||||
return !!item;
|
||||
};
|
||||
|
||||
export const getSources = (alert: TopAlert): Group[] => {
|
||||
// when `kibana.alert.group` is not flattened (for alert detail pages)
|
||||
if (alert.fields[ALERT_GROUP]) return alert.fields[ALERT_GROUP];
|
||||
if (alert.fields[ALERT_GROUP]) return alert.fields[ALERT_GROUP] as Group[];
|
||||
|
||||
// when `kibana.alert.group` is flattened (for alert flyout)
|
||||
const groupsFromGroupFields = alert.fields[ALERT_GROUP_FIELD]?.map((field, index) => {
|
||||
const values = alert.fields[ALERT_GROUP_VALUE];
|
||||
if (values?.length && values[index]) {
|
||||
return { field, value: values[index] };
|
||||
const group: Group = { field, value: values[index] };
|
||||
return group;
|
||||
}
|
||||
});
|
||||
}).filter(isGroup);
|
||||
|
||||
if (groupsFromGroupFields?.length) return groupsFromGroupFields;
|
||||
|
||||
// Not all rules has group.fields, in that case we search in the alert fields.
|
||||
const matchedSources: Array<{ field: string; value: any }> = [];
|
||||
const matchedSources: Group[] = [];
|
||||
const ALL_SOURCES = [...infraSources, ...apmSources];
|
||||
const alertFields = alert.fields as AlertFields;
|
||||
ALL_SOURCES.forEach((source: string) => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { SERVICE_NAME } from '@kbn/observability-shared-plugin/common';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
|
@ -54,7 +54,7 @@ export function Groups({ groups, timeRange }: { groups: Group[]; timeRange: Time
|
|||
{groups &&
|
||||
groups.map((group) => {
|
||||
return (
|
||||
<span key={group.field}>
|
||||
<EuiText key={group.field}>
|
||||
{group.field}:{' '}
|
||||
{sourceLinks[group.field] ? (
|
||||
<EuiLink data-test-subj="o11yAlertSourceLink" href={sourceLinks[group.field]}>
|
||||
|
@ -63,8 +63,7 @@ export function Groups({ groups, timeRange }: { groups: Group[]; timeRange: Time
|
|||
) : (
|
||||
<strong>{group.value}</strong>
|
||||
)}
|
||||
<br />
|
||||
</span>
|
||||
</EuiText>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -103,7 +103,7 @@ describe('AlertDetailsAppSection', () => {
|
|||
const result = renderComponent();
|
||||
|
||||
expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(6);
|
||||
expect(result.getByTestId('thresholdRule-2000-2500')).toBeTruthy();
|
||||
expect(result.getByTestId('threshold-2000-2500')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render annotations', async () => {
|
||||
|
|
|
@ -38,7 +38,7 @@ import { useLicense } from '../../../../hooks/use_license';
|
|||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter';
|
||||
import { AlertParams } from '../../types';
|
||||
import { Threshold } from '../custom_threshold';
|
||||
import { Threshold } from '../threshold';
|
||||
import { CustomThresholdRule, CustomThresholdAlert } from '../types';
|
||||
import { LogRateAnalysis } from './log_rate_analysis';
|
||||
import { RuleConditionChart } from '../../../rule_condition_chart/rule_condition_chart';
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
import { ComponentMeta } from '@storybook/react';
|
||||
import { LIGHT_THEME } from '@elastic/charts';
|
||||
import { COMPARATORS } from '@kbn/alerting-comparators';
|
||||
import { Props, Threshold as Component } from './custom_threshold';
|
||||
import { Props, Threshold as Component } from './threshold';
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
|
@ -35,7 +35,7 @@ const defaultProps: Props = {
|
|||
threshold: [90],
|
||||
title: 'Threshold breached',
|
||||
value: 93,
|
||||
valueFormatter: (d) => `${d}%`,
|
||||
valueFormatter: (d: number) => `${d}%`,
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import { LIGHT_THEME } from '@elastic/charts';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { Props, Threshold } from './custom_threshold';
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { COMPARATORS } from '@kbn/alerting-comparators';
|
||||
import { Props, Threshold } from './threshold';
|
||||
|
||||
describe('Threshold', () => {
|
||||
const renderComponent = (props: Partial<Props> = {}) => {
|
||||
|
@ -38,7 +38,7 @@ describe('Threshold', () => {
|
|||
|
||||
it('shows component', () => {
|
||||
const component = renderComponent();
|
||||
expect(component.queryByTestId('thresholdRule-90-93')).toBeTruthy();
|
||||
expect(component.queryByTestId('threshold-90-93')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows component for between', () => {
|
||||
|
@ -46,6 +46,6 @@ describe('Threshold', () => {
|
|||
comparator: COMPARATORS.BETWEEN,
|
||||
threshold: [90, 95],
|
||||
});
|
||||
expect(component.queryByTestId('thresholdRule-90-95-93')).toBeTruthy();
|
||||
expect(component.queryByTestId('threshold-90-95-93')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -6,14 +6,14 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Chart, Metric, Settings } from '@elastic/charts';
|
||||
import { Chart, Metric, Settings, ValueFormatter } from '@elastic/charts';
|
||||
import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui';
|
||||
import type { PartialTheme, Theme } from '@elastic/charts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { COMPARATORS } from '@kbn/alerting-comparators';
|
||||
|
||||
export interface ChartProps {
|
||||
theme?: PartialTheme;
|
||||
theme?: PartialTheme[];
|
||||
baseTheme: Theme;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ export interface Props {
|
|||
threshold: number[];
|
||||
title: string;
|
||||
value: number;
|
||||
valueFormatter: (d: number) => string;
|
||||
valueFormatter?: ValueFormatter;
|
||||
}
|
||||
|
||||
export function Threshold({
|
||||
|
@ -34,7 +34,7 @@ export function Threshold({
|
|||
threshold,
|
||||
title,
|
||||
value,
|
||||
valueFormatter,
|
||||
valueFormatter = (d) => String(d),
|
||||
}: Props) {
|
||||
const color = useEuiBackgroundColor('danger');
|
||||
|
||||
|
@ -42,13 +42,14 @@ export function Threshold({
|
|||
<EuiPanel
|
||||
paddingSize="none"
|
||||
style={{
|
||||
height: '100%',
|
||||
height: '170px',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
minWidth: '100%',
|
||||
}}
|
||||
hasShadow={false}
|
||||
data-test-subj={`thresholdRule-${threshold.join('-')}-${value}`}
|
||||
data-test-subj={`threshold-${threshold.join('-')}-${value}`}
|
||||
grow={false}
|
||||
>
|
||||
<Chart>
|
||||
<Settings theme={theme} baseTheme={baseTheme} locale={i18n.getLocale()} />
|
|
@ -63,9 +63,9 @@ export const LazyAlertsFlyout = lazy(() => import('./components/alerts_flyout/al
|
|||
|
||||
export * from './typings';
|
||||
import { TopAlert } from './typings/alerts';
|
||||
import { AlertSummary } from './pages/alert_details/components';
|
||||
import type { AlertSummaryField } from './pages/alert_details/components/alert_summary';
|
||||
export type { TopAlert, AlertSummary, AlertSummaryField };
|
||||
export type { TopAlert };
|
||||
import type { AlertDetailsAppSectionProps } from './pages/alert_details/types';
|
||||
export type { AlertDetailsAppSectionProps };
|
||||
|
||||
export { observabilityFeatureId, observabilityAppId } from '../common';
|
||||
|
||||
|
@ -102,3 +102,4 @@ export { useAnnotations } from './components/annotations/use_annotations';
|
|||
export { RuleConditionChart } from './components/rule_condition_chart';
|
||||
export { getGroupFilters } from '../common/custom_threshold_rule/helpers/get_group';
|
||||
export type { GenericAggType } from './components/rule_condition_chart/rule_condition_chart';
|
||||
export { Threshold } from './components/custom_threshold/components/threshold';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { casesPluginMock } from '@kbn/cases-plugin/public/mocks';
|
|||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting';
|
||||
import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assistant-plugin/public/mock';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
import { useBreadcrumbs, TagsList } from '@kbn/observability-shared-plugin/public';
|
||||
import { RuleTypeModel, ValidationResult } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { ruleTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/rule_type_registry.mock';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
@ -88,6 +88,7 @@ const useParamsMock = useParams as jest.Mock;
|
|||
const useLocationMock = useLocation as jest.Mock;
|
||||
const useHistoryMock = useHistory as jest.Mock;
|
||||
const useBreadcrumbsMock = useBreadcrumbs as jest.Mock;
|
||||
const TagsListMock = TagsList as jest.Mock;
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
|
@ -114,6 +115,7 @@ describe('Alert details', () => {
|
|||
useLocationMock.mockReturnValue({ pathname: '/alerts/uuid', search: '', state: '', hash: '' });
|
||||
useHistoryMock.mockReturnValue({ replace: jest.fn() });
|
||||
useBreadcrumbsMock.mockReturnValue([]);
|
||||
TagsListMock.mockReturnValue(<div data-test-subj="TagsList" />);
|
||||
ruleTypeRegistry.list.mockReturnValue([ruleType]);
|
||||
ruleTypeRegistry.get.mockReturnValue(ruleType);
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
EuiTabbedContentTab,
|
||||
useEuiTheme,
|
||||
EuiFlexGroup,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
AlertStatus,
|
||||
|
@ -31,15 +32,16 @@ import dedent from 'dedent';
|
|||
import { AlertFieldsTable } from '@kbn/alerts-ui-shared';
|
||||
import { css } from '@emotion/react';
|
||||
import { omit } from 'lodash';
|
||||
import { AlertDetailsSource } from './types';
|
||||
import { SourceBar } from './components';
|
||||
import { StatusBar } from './components/status_bar';
|
||||
import { observabilityFeatureId } from '../../../common';
|
||||
import { RelatedAlerts } from './components/related_alerts';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { useFetchRule } from '../../hooks/use_fetch_rule';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { AlertData, useFetchAlertDetail } from '../../hooks/use_fetch_alert_detail';
|
||||
import { PageTitleContent } from './components/page_title_content';
|
||||
import { HeaderActions } from './components/header_actions';
|
||||
import { AlertSummary, AlertSummaryField } from './components/alert_summary';
|
||||
import { CenterJustifiedSpinner } from '../../components/center_justified_spinner';
|
||||
import { getTimeZone } from '../../utils/get_time_zone';
|
||||
import { isAlertDetailsEnabledPerApp } from '../../utils/is_alert_details_enabled';
|
||||
|
@ -103,10 +105,10 @@ export function AlertDetails() {
|
|||
const { rule } = useFetchRule({
|
||||
ruleId,
|
||||
});
|
||||
const [summaryFields, setSummaryFields] = useState<AlertSummaryField[]>();
|
||||
const [alertStatus, setAlertStatus] = useState<AlertStatus>();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const [sources, setSources] = useState<AlertDetailsSource[]>();
|
||||
const [relatedAlertsKuery, setRelatedAlertsKuery] = useState<string>();
|
||||
const [activeTabId, setActiveTabId] = useState<TabId>(() => {
|
||||
const searchParams = new URLSearchParams(search);
|
||||
|
@ -212,17 +214,19 @@ export function AlertDetails() {
|
|||
*/
|
||||
isAlertDetailsEnabledPerApp(alertDetail.formatted, config) ? (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<AlertSummary alert={alertDetail.formatted} alertSummaryFields={summaryFields} />
|
||||
<AlertDetailContextualInsights alert={alertDetail} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<SourceBar alert={alertDetail.formatted} sources={sources} />
|
||||
<AlertDetailContextualInsights alert={alertDetail} />
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
{rule && alertDetail.formatted && (
|
||||
<>
|
||||
<AlertDetailsAppSection
|
||||
alert={alertDetail.formatted}
|
||||
rule={rule}
|
||||
timeZone={timeZone}
|
||||
setAlertSummaryFields={setSummaryFields}
|
||||
setSources={setSources}
|
||||
setRelatedAlertsKuery={setRelatedAlertsKuery}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
|
@ -290,13 +294,6 @@ export function AlertDetails() {
|
|||
) : (
|
||||
<EuiLoadingSpinner />
|
||||
),
|
||||
children: (
|
||||
<PageTitleContent
|
||||
alert={alertDetail?.formatted ?? null}
|
||||
alertStatus={alertStatus}
|
||||
dataTestSubj={rule?.ruleTypeId || 'alertDetailsPageTitle'}
|
||||
/>
|
||||
),
|
||||
rightSideItems: [
|
||||
<CasesContext
|
||||
owner={[observabilityFeatureId]}
|
||||
|
@ -312,6 +309,7 @@ export function AlertDetails() {
|
|||
</CasesContext>,
|
||||
],
|
||||
bottomBorder: false,
|
||||
'data-test-subj': rule?.ruleTypeId || 'alertDetailsPageTitle',
|
||||
}}
|
||||
pageSectionProps={{
|
||||
paddingSize: 'none',
|
||||
|
@ -321,6 +319,8 @@ export function AlertDetails() {
|
|||
}}
|
||||
data-test-subj="alertDetails"
|
||||
>
|
||||
<StatusBar alert={alertDetail?.formatted ?? null} alertStatus={alertStatus} />
|
||||
<EuiSpacer size="l" />
|
||||
<HeaderMenu />
|
||||
<EuiTabbedContent
|
||||
data-test-subj="alertDetailsTabbedContent"
|
||||
|
|
|
@ -1,80 +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 React from 'react';
|
||||
import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting';
|
||||
import { render } from '../../../utils/test_helper';
|
||||
import { AlertSummary } from './alert_summary';
|
||||
import { alertWithGroupsAndTags } from '../mock/alert';
|
||||
import { alertSummaryFieldsMock } from '../mock/alert_summary_fields';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
import { Group } from '../../../../common/typings';
|
||||
import {
|
||||
ALERT_EVALUATION_THRESHOLD,
|
||||
ALERT_EVALUATION_VALUE,
|
||||
ALERT_GROUP,
|
||||
ALERT_RULE_NAME,
|
||||
TAGS,
|
||||
} from '@kbn/rule-data-utils';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/kibana_react');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
|
||||
const mockKibana = () => {
|
||||
useKibanaMock.mockReturnValue({
|
||||
services: {
|
||||
...kibanaStartMock.startContract().services,
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('Alert summary', () => {
|
||||
jest
|
||||
.spyOn(useUiSettingHook, 'useUiSetting')
|
||||
.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibana();
|
||||
});
|
||||
|
||||
it('should show alert data', async () => {
|
||||
const alertSummary = render(
|
||||
<AlertSummary alert={alertWithGroupsAndTags} alertSummaryFields={alertSummaryFieldsMock} />
|
||||
);
|
||||
|
||||
const groups = alertWithGroupsAndTags.fields[ALERT_GROUP] as Group[];
|
||||
|
||||
expect(alertSummary.queryByText('Source')).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(groups[0].field, { exact: false })).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(groups[0].value)).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(groups[1].field, { exact: false })).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(groups[1].value)).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText('Tags')).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(alertWithGroupsAndTags.fields[TAGS]![0])).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText('Rule')).toBeInTheDocument();
|
||||
expect(
|
||||
alertSummary.queryByText(alertWithGroupsAndTags.fields[ALERT_RULE_NAME])
|
||||
).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText('Actual value')).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(alertWithGroupsAndTags.fields[ALERT_EVALUATION_VALUE]!));
|
||||
expect(alertSummary.queryByText('Expected value')).toBeInTheDocument();
|
||||
expect(alertSummary.queryByText(alertWithGroupsAndTags.fields[ALERT_EVALUATION_THRESHOLD]!));
|
||||
});
|
||||
});
|
|
@ -1,115 +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 React, { useEffect, useState, ReactNode } from 'react';
|
||||
import { EuiFlexItem, EuiFlexGroup, EuiText, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
|
||||
import {
|
||||
TAGS,
|
||||
ALERT_START,
|
||||
ALERT_END,
|
||||
ALERT_RULE_NAME,
|
||||
ALERT_RULE_UUID,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { TopAlert } from '../../..';
|
||||
import { Groups } from '../../../components/alert_sources/groups';
|
||||
import { Tags } from '../../../components/tags';
|
||||
import { getSources } from '../../../components/alert_sources/get_sources';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
|
||||
export interface AlertSummaryField {
|
||||
label: ReactNode | string;
|
||||
value: ReactNode | string | number;
|
||||
}
|
||||
export interface AlertSummaryProps {
|
||||
alert: TopAlert;
|
||||
alertSummaryFields?: AlertSummaryField[];
|
||||
}
|
||||
|
||||
export function AlertSummary({ alert, alertSummaryFields }: AlertSummaryProps) {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>({ from: 'now-15m', to: 'now' });
|
||||
|
||||
const alertStart = alert.fields[ALERT_START];
|
||||
const alertEnd = alert.fields[ALERT_END];
|
||||
const ruleName = alert.fields[ALERT_RULE_NAME];
|
||||
const ruleId = alert.fields[ALERT_RULE_UUID];
|
||||
const tags = alert.fields[TAGS];
|
||||
|
||||
const ruleLink = http.basePath.prepend(paths.observability.ruleDetails(ruleId));
|
||||
const commonFieldsAtStart = [];
|
||||
const commonFieldsAtEnd = [];
|
||||
const groups = getSources(alert) as Array<{ field: string; value: string }>;
|
||||
|
||||
useEffect(() => {
|
||||
setTimeRange(getPaddedAlertTimeRange(alertStart!, alertEnd));
|
||||
}, [alertStart, alertEnd]);
|
||||
|
||||
if (groups && groups.length > 0) {
|
||||
commonFieldsAtStart.push({
|
||||
label: i18n.translate('xpack.observability.alertDetails.alertSummaryField.source', {
|
||||
defaultMessage: 'Source',
|
||||
}),
|
||||
value: (
|
||||
<Groups groups={groups} timeRange={alertEnd ? timeRange : { ...timeRange, to: 'now' }} />
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (tags && tags.length > 0) {
|
||||
commonFieldsAtEnd.push({
|
||||
label: i18n.translate('xpack.observability.alertDetails.alertSummaryField.tags', {
|
||||
defaultMessage: 'Tags',
|
||||
}),
|
||||
value: <Tags tags={tags} />,
|
||||
});
|
||||
}
|
||||
|
||||
commonFieldsAtEnd.push({
|
||||
label: i18n.translate('xpack.observability.alertDetails.alertSummaryField.rule', {
|
||||
defaultMessage: 'Rule',
|
||||
}),
|
||||
value: (
|
||||
<EuiLink data-test-subj="o11yAlertRuleLink" href={ruleLink}>
|
||||
{ruleName}
|
||||
</EuiLink>
|
||||
),
|
||||
});
|
||||
|
||||
const alertSummary = [
|
||||
...commonFieldsAtStart,
|
||||
...(alertSummaryFields ?? []),
|
||||
...commonFieldsAtEnd,
|
||||
];
|
||||
|
||||
return (
|
||||
<div data-test-subj="alert-summary-container">
|
||||
{alertSummary && alertSummary.length > 0 && (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xl">
|
||||
{alertSummary.map((field, idx) => {
|
||||
return (
|
||||
<EuiFlexItem key={idx} grow={false}>
|
||||
<EuiText color="subdued">{field.label}</EuiText>
|
||||
<EuiText>{field.value}</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AlertSummary;
|
|
@ -6,14 +6,14 @@
|
|||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import type { AlertSummaryProps } from './alert_summary';
|
||||
import type { SourceBarProps } from './source_bar';
|
||||
|
||||
const AlertSummaryLazy = lazy(() => import('./alert_summary'));
|
||||
const SourceBarLazy = lazy(() => import('./source_bar'));
|
||||
|
||||
export function AlertSummary(props: AlertSummaryProps) {
|
||||
export function SourceBar(props: SourceBarProps) {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<AlertSummaryLazy {...props} />
|
||||
<SourceBarLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,43 +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 React from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { EuiPageTemplate } from '@elastic/eui';
|
||||
|
||||
import { PageTitleContent as Component, PageTitleContentProps } from './page_title_content';
|
||||
import { alert } from '../mock/alert';
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
title: 'app/AlertDetails/PageTitleContent',
|
||||
alert,
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Component> = (props: PageTitleContentProps) => (
|
||||
<Component {...props} />
|
||||
);
|
||||
|
||||
const TemplateWithPageTemplate: ComponentStory<typeof Component> = (
|
||||
props: PageTitleContentProps
|
||||
) => (
|
||||
<EuiPageTemplate>
|
||||
<EuiPageTemplate.Header children={<Component {...props} />} bottomBorder={false} />
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
|
||||
const defaultProps = {
|
||||
alert,
|
||||
};
|
||||
|
||||
export const PageTitleContent = Template.bind({});
|
||||
PageTitleContent.args = defaultProps;
|
||||
|
||||
export const PageTitleUsedWithinPageTemplate = TemplateWithPageTemplate.bind({});
|
||||
PageTitleUsedWithinPageTemplate.args = {
|
||||
...defaultProps,
|
||||
};
|
|
@ -1,74 +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 React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import {
|
||||
AlertStatus,
|
||||
ALERT_STATUS,
|
||||
ALERT_STATUS_ACTIVE,
|
||||
ALERT_STATUS_RECOVERED,
|
||||
ALERT_STATUS_UNTRACKED,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { PageTitleContent, PageTitleContentProps } from './page_title_content';
|
||||
import { alert } from '../mock/alert';
|
||||
|
||||
describe('Page Title Content', () => {
|
||||
const defaultProps = {
|
||||
alert,
|
||||
alertStatus: ALERT_STATUS_ACTIVE as AlertStatus,
|
||||
dataTestSubj: 'ruleTypeId',
|
||||
};
|
||||
|
||||
const renderComp = (props: PageTitleContentProps) => {
|
||||
return render(
|
||||
<IntlProvider locale="en">
|
||||
<PageTitleContent {...props} />
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('should display an active badge when alert is active', async () => {
|
||||
const { getByText } = renderComp(defaultProps);
|
||||
expect(getByText('Active')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display a recovered badge when alert is recovered', async () => {
|
||||
const updatedProps = {
|
||||
alert: {
|
||||
...defaultProps.alert,
|
||||
fields: {
|
||||
...defaultProps.alert.fields,
|
||||
[ALERT_STATUS]: ALERT_STATUS_RECOVERED,
|
||||
},
|
||||
},
|
||||
alertStatus: ALERT_STATUS_RECOVERED as AlertStatus,
|
||||
dataTestSubj: defaultProps.dataTestSubj,
|
||||
};
|
||||
|
||||
const { getByText } = renderComp({ ...updatedProps });
|
||||
expect(getByText('Recovered')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display an untracked badge when alert is untracked', async () => {
|
||||
const updatedProps = {
|
||||
alert: {
|
||||
...defaultProps.alert,
|
||||
fields: {
|
||||
...defaultProps.alert.fields,
|
||||
[ALERT_STATUS]: ALERT_STATUS_UNTRACKED,
|
||||
},
|
||||
},
|
||||
alertStatus: ALERT_STATUS_UNTRACKED as AlertStatus,
|
||||
dataTestSubj: defaultProps.dataTestSubj,
|
||||
};
|
||||
|
||||
const { getByText } = renderComp({ ...updatedProps });
|
||||
expect(getByText('Untracked')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { EuiLink } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ALERT_GROUP } from '@kbn/rule-data-utils';
|
||||
import { render } from '../../../utils/test_helper';
|
||||
import { alertWithGroupsAndTags } from '../mock/alert';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
import { Group } from '../../../../common/typings';
|
||||
import { SourceBar } from './source_bar';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/kibana_react');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
const mockKibana = () => {
|
||||
useKibanaMock.mockReturnValue({
|
||||
services: {
|
||||
...kibanaStartMock.startContract().services,
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('Source bar', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibana();
|
||||
});
|
||||
|
||||
it('should show alert data', async () => {
|
||||
const sourceBar = render(<SourceBar alert={alertWithGroupsAndTags} />);
|
||||
|
||||
const groups = alertWithGroupsAndTags.fields[ALERT_GROUP] as Group[];
|
||||
|
||||
expect(sourceBar.queryByText('Source')).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(groups[0].field, { exact: false })).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(groups[0].value)).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(groups[1].field, { exact: false })).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(groups[1].value)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should show passed sources', async () => {
|
||||
const sources = [
|
||||
{ label: 'MyLabel', value: 'MyValue' },
|
||||
{ label: 'SLO', value: <EuiLink data-test-subj="SourceSloLink" href="href" /> },
|
||||
];
|
||||
const sourceBar = render(<SourceBar alert={alertWithGroupsAndTags} sources={sources} />);
|
||||
|
||||
expect(sourceBar.queryByText('Source')).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(sources[0].label, { exact: false })).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(sources[0].value as string, { exact: false })).toBeInTheDocument();
|
||||
expect(sourceBar.queryByText(sources[1].label, { exact: false })).toBeInTheDocument();
|
||||
expect(sourceBar.queryByTestId('SourceSloLink')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiTitle, EuiPanel, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
|
||||
import { ALERT_START, ALERT_END } from '@kbn/rule-data-utils';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { AlertDetailsSource } from '../types';
|
||||
import { TopAlert } from '../../..';
|
||||
import { Groups } from '../../../components/alert_sources/groups';
|
||||
import { getSources } from '../../../components/alert_sources/get_sources';
|
||||
|
||||
export interface SourceBarProps {
|
||||
alert: TopAlert;
|
||||
sources?: AlertDetailsSource[];
|
||||
}
|
||||
|
||||
export function SourceBar({ alert, sources = [] }: SourceBarProps) {
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>({ from: 'now-15m', to: 'now' });
|
||||
|
||||
const alertStart = alert.fields[ALERT_START];
|
||||
const alertEnd = alert.fields[ALERT_END];
|
||||
const groups = getSources(alert);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeRange(getPaddedAlertTimeRange(alertStart!, alertEnd));
|
||||
}, [alertStart, alertEnd]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{groups && groups.length > 0 && (
|
||||
<EuiPanel data-test-subj="alert-summary-container" hasShadow={false} hasBorder={true}>
|
||||
<EuiFlexGroup gutterSize="l" direction="row" wrap>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.alertDetails.sourceBar.source"
|
||||
defaultMessage="Source"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<Groups
|
||||
groups={groups}
|
||||
timeRange={alertEnd ? timeRange : { ...timeRange, to: 'now' }}
|
||||
/>
|
||||
{sources.map((field, idx) => {
|
||||
return (
|
||||
<EuiFlexItem key={`sources-${idx}`} grow={false}>
|
||||
<EuiText>
|
||||
{field.label}: {field.value}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default SourceBar;
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { StatusBar as Component, StatusBarProps } from './status_bar';
|
||||
import { alert } from '../mock/alert';
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
title: 'app/AlertDetails/StatusBar',
|
||||
alert,
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Component> = (props: StatusBarProps) => (
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={services}>
|
||||
<Component {...props} />
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
const defaultProps = {
|
||||
alert,
|
||||
};
|
||||
|
||||
export const StatusBar = Template.bind({});
|
||||
StatusBar.args = defaultProps;
|
||||
|
||||
const services = {
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: () => 'http://test',
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import {
|
||||
ALERT_RULE_NAME,
|
||||
ALERT_STATUS,
|
||||
ALERT_STATUS_RECOVERED,
|
||||
ALERT_STATUS_UNTRACKED,
|
||||
AlertStatus,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { render } from '../../../utils/test_helper';
|
||||
import { alertWithGroupsAndTags } from '../mock/alert';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
import { StatusBar, StatusBarProps } from './status_bar';
|
||||
|
||||
jest.mock('../../../utils/kibana_react');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
const mockKibana = () => {
|
||||
useKibanaMock.mockReturnValue({
|
||||
services: {
|
||||
...kibanaStartMock.startContract().services,
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('Source bar', () => {
|
||||
const renderComponent = (props: StatusBarProps) => {
|
||||
return render(<StatusBar {...props} />);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibana();
|
||||
});
|
||||
|
||||
it('should show alert data', async () => {
|
||||
const statusBar = renderComponent({
|
||||
alert: alertWithGroupsAndTags,
|
||||
alertStatus: alertWithGroupsAndTags.fields[ALERT_STATUS] as AlertStatus,
|
||||
});
|
||||
|
||||
expect(
|
||||
statusBar.queryByText(alertWithGroupsAndTags.fields[ALERT_RULE_NAME])
|
||||
).toBeInTheDocument();
|
||||
expect(statusBar.getByText('Active')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display a recovered badge when alert is recovered', async () => {
|
||||
const updatedProps = {
|
||||
alert: {
|
||||
...alertWithGroupsAndTags,
|
||||
fields: {
|
||||
...alertWithGroupsAndTags.fields,
|
||||
[ALERT_STATUS]: ALERT_STATUS_RECOVERED,
|
||||
},
|
||||
},
|
||||
alertStatus: ALERT_STATUS_RECOVERED as AlertStatus,
|
||||
};
|
||||
|
||||
const { getByText } = renderComponent({ ...updatedProps });
|
||||
expect(getByText('Recovered')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display an untracked badge when alert is untracked', async () => {
|
||||
const updatedProps = {
|
||||
alert: {
|
||||
...alertWithGroupsAndTags,
|
||||
fields: {
|
||||
...alertWithGroupsAndTags.fields,
|
||||
[ALERT_STATUS]: ALERT_STATUS_UNTRACKED,
|
||||
},
|
||||
},
|
||||
alertStatus: ALERT_STATUS_UNTRACKED as AlertStatus,
|
||||
};
|
||||
|
||||
const { getByText } = renderComponent({ ...updatedProps });
|
||||
expect(getByText('Untracked')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -7,30 +7,51 @@
|
|||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, useEuiTheme, EuiToolTip } from '@elastic/eui';
|
||||
import { AlertLifecycleStatusBadge } from '@kbn/alerts-ui-shared';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { AlertStatus, ALERT_DURATION, ALERT_FLAPPING, TIMESTAMP } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
AlertStatus,
|
||||
ALERT_DURATION,
|
||||
ALERT_FLAPPING,
|
||||
TIMESTAMP,
|
||||
TAGS,
|
||||
ALERT_RULE_NAME,
|
||||
ALERT_RULE_UUID,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { css } from '@emotion/react';
|
||||
import { TagsList } from '@kbn/observability-shared-plugin/public';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
import { asDuration } from '../../../../common/utils/formatters';
|
||||
import { TopAlert } from '../../../typings/alerts';
|
||||
|
||||
export interface PageTitleContentProps {
|
||||
export interface StatusBarProps {
|
||||
alert: TopAlert | null;
|
||||
alertStatus?: AlertStatus;
|
||||
dataTestSubj: string;
|
||||
}
|
||||
|
||||
export function PageTitleContent({ alert, alertStatus, dataTestSubj }: PageTitleContentProps) {
|
||||
export function StatusBar({ alert, alertStatus }: StatusBarProps) {
|
||||
const { http } = useKibana().services;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const tags = alert?.fields[TAGS];
|
||||
const ruleName = alert?.fields[ALERT_RULE_NAME];
|
||||
const ruleId = alert?.fields[ALERT_RULE_UUID];
|
||||
const ruleLink = ruleId ? http.basePath.prepend(paths.observability.ruleDetails(ruleId)) : '';
|
||||
|
||||
if (!alert) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="row" alignItems="center" gutterSize="xl" data-test-subj={dataTestSubj}>
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
data-test-subj="statusBar"
|
||||
wrap
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
{alertStatus && (
|
||||
<AlertLifecycleStatusBadge
|
||||
|
@ -40,6 +61,43 @@ export function PageTitleContent({ alert, alertStatus, dataTestSubj }: PageTitle
|
|||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TagsList tags={tags} ignoreEmpty color="default" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 160 }}>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="center">
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.observability.pages.alertDetails.pageTitle.ruleName"
|
||||
defaultMessage="Rule"
|
||||
/>
|
||||
:
|
||||
</EuiText>
|
||||
<EuiToolTip position="top" content={ruleName}>
|
||||
<EuiText
|
||||
css={css`
|
||||
font-weight: ${euiTheme.font.weight.semiBold};
|
||||
`}
|
||||
size="s"
|
||||
>
|
||||
<EuiLink
|
||||
data-test-subj="o11yAlertRuleLink"
|
||||
href={ruleLink}
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '200px',
|
||||
display: 'flow',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{ruleName}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 100 }}>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
|
@ -58,7 +116,7 @@ export function PageTitleContent({ alert, alertStatus, dataTestSubj }: PageTitle
|
|||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 120 }}>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
|
@ -77,7 +135,7 @@ export function PageTitleContent({ alert, alertStatus, dataTestSubj }: PageTitle
|
|||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 240 }}>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
|
@ -1,21 +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 { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils';
|
||||
import { alertWithGroupsAndTags } from './alert';
|
||||
import type { AlertSummaryField } from '../components/alert_summary';
|
||||
|
||||
export const alertSummaryFieldsMock: AlertSummaryField[] = [
|
||||
{
|
||||
label: 'Actual value',
|
||||
value: alertWithGroupsAndTags.fields[ALERT_EVALUATION_VALUE]!,
|
||||
},
|
||||
{
|
||||
label: 'Expected value',
|
||||
value: alertWithGroupsAndTags.fields[ALERT_EVALUATION_THRESHOLD]!,
|
||||
},
|
||||
];
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { ReactNode } from 'react';
|
||||
|
||||
export interface AlertDetailsSource {
|
||||
label: ReactNode | string;
|
||||
value: ReactNode | string | number;
|
||||
}
|
||||
|
||||
export interface AlertDetailsAppSectionProps {
|
||||
setSources: React.Dispatch<React.SetStateAction<AlertDetailsSource[] | undefined>>;
|
||||
}
|
|
@ -4,28 +4,24 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { EuiFlexGroup, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertSummaryField } from '@kbn/observability-plugin/public';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFetchSloDetails } from '../../../../hooks/use_fetch_slo_details';
|
||||
import { AlertDetailsAppSectionProps } from '@kbn/observability-plugin/public';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { useFetchSloDetails } from '../../../../hooks/use_fetch_slo_details';
|
||||
import { CustomAlertDetailsPanel } from './components/custom_panels/custom_panels';
|
||||
import { ErrorRatePanel } from './components/error_rate/error_rate_panel';
|
||||
import { BurnRateAlert, BurnRateRule } from './types';
|
||||
|
||||
interface AppSectionProps {
|
||||
interface AppSectionProps extends AlertDetailsAppSectionProps {
|
||||
alert: BurnRateAlert;
|
||||
rule: BurnRateRule;
|
||||
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function AlertDetailsAppSection({
|
||||
alert,
|
||||
rule,
|
||||
setAlertSummaryFields,
|
||||
}: AppSectionProps) {
|
||||
export default function AlertDetailsAppSection({ alert, rule, setSources }: AppSectionProps) {
|
||||
const {
|
||||
services: {
|
||||
http: { basePath },
|
||||
|
@ -51,8 +47,8 @@ export default function AlertDetailsAppSection({
|
|||
},
|
||||
];
|
||||
|
||||
setAlertSummaryFields(fields);
|
||||
}, [alertLink, rule, setAlertSummaryFields, basePath, slo, instanceId]);
|
||||
setSources(fields);
|
||||
}, [alertLink, rule, setSources, basePath, slo, instanceId]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" data-test-subj="overviewSection">
|
||||
|
|
|
@ -10774,8 +10774,6 @@
|
|||
"xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint": "Le nom de l'environnement dans lequel ce service est déployé, par exemple \"production\" ou \"test\". Les environnements vous permettent de facilement filtrer les données à un niveau global dans l'interface utilisateur APM. Il est important de garantir la cohérence des noms d'environnements entre les différents agents.",
|
||||
"xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "Le nom de service est le filtre principal dans l'interface utilisateur APM et est utilisé pour regrouper les erreurs et suivre les données ensemble. Caractères autorisés : a-z, A-Z, 0-9, -, _ et espace.",
|
||||
"xpack.apm.onboarding.specProvider.longDescription": "Le monitoring des performances applicatives (APM) collecte les indicateurs et les erreurs de performance approfondies depuis votre application. Cela vous permet de monitorer les performances de milliers d'applications en temps réel. {learnMoreLink}.",
|
||||
"xpack.apm.pages.alertDetails.alertSummary.actualValue": "Valeur réelle",
|
||||
"xpack.apm.pages.alertDetails.alertSummary.expectedValue": "Valeur attendue",
|
||||
"xpack.apm.percentOfParent": "({value} de {parentType, select, transaction { transaction } trace {trace} other {parentType inconnu} })",
|
||||
"xpack.apm.profiling.callout.description": "Universal Profiling fournit une visibilité sans précédent du code au milieu du comportement en cours d'exécution de toutes les applications. La fonctionnalité profile chaque ligne de code chez le ou les hôtes qui exécutent vos services, y compris votre code applicatif, le kernel et même les bibliothèque tierces.",
|
||||
"xpack.apm.profiling.callout.dismiss": "Rejeter",
|
||||
|
@ -32063,9 +32061,6 @@
|
|||
"xpack.observability.alertDetailContextualInsights.InsightButtonLabel": "Aidez moi à comprendre cette alerte",
|
||||
"xpack.observability.alertDetails.actionsButtonLabel": "Actions",
|
||||
"xpack.observability.alertDetails.addToCase": "Ajouter au cas",
|
||||
"xpack.observability.alertDetails.alertSummaryField.rule": "Règle",
|
||||
"xpack.observability.alertDetails.alertSummaryField.source": "Source",
|
||||
"xpack.observability.alertDetails.alertSummaryField.tags": "Balises",
|
||||
"xpack.observability.alertDetails.editRule": "Modifier la règle",
|
||||
"xpack.observability.alertDetails.editSnoozeRule": "Répéter la règle",
|
||||
"xpack.observability.alertDetails.errorPromptBody": "Une erreur s'est produite lors du chargement des détails de l'alerte.",
|
||||
|
@ -41229,7 +41224,6 @@
|
|||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInHours": "{duration} dernières heures",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInMinutes": "{duration} dernières minutes",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInSeconds": "{duration} dernières secondes",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.summaryField.slo": "SLO",
|
||||
"xpack.slo.burnRateRuleEditor.h5.chooseASLOToMonitorLabel": "Choisir un SLO pour monitorer",
|
||||
"xpack.slo.burnRateRuleEditor.h5.defineMultipleBurnRateLabel": "Définir des fenêtres du taux d'avancement multiples",
|
||||
"xpack.slo.burnRates.fromRange.label": "{duration}h",
|
||||
|
|
|
@ -10523,8 +10523,6 @@
|
|||
"xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint": "このサービスがデプロイされている環境の名前(例:「本番」、「ステージング」)。環境では、APM UIでグローバルレベルで簡単にデータをフィルタリングできます。すべてのエージェントで環境の命名方法を統一することが重要です。",
|
||||
"xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "このサービス名はAPM UIの主フィルターであり、エラーとトレースデータをグループ化するために使用されます。使用できる文字はA-Z、0-9、-、_、スペースです。",
|
||||
"xpack.apm.onboarding.specProvider.longDescription": "アプリケーションパフォーマンスモニタリング(APM)は、アプリケーション内から詳細なパフォーマンスメトリックやエラーを収集します。何千ものアプリケーションのパフォーマンスをリアルタイムで監視できます。{learnMoreLink}。",
|
||||
"xpack.apm.pages.alertDetails.alertSummary.actualValue": "実際の値",
|
||||
"xpack.apm.pages.alertDetails.alertSummary.expectedValue": "想定された値",
|
||||
"xpack.apm.percentOfParent": "({value} of {parentType, select, transaction { トランザクション } trace {トレース} other {不明なparentType} })",
|
||||
"xpack.apm.profiling.callout.description": "ユニバーサルプロファイリングは、すべてのアプリケーションの実行時の動作に関して、かつてないほどコードを可視化します。アプリケーションコードだけでなく、カーネルやサードパーティライブラリも含め、サービスを実行するホスト上のすべてのコード行をプロファイリングします。",
|
||||
"xpack.apm.profiling.callout.dismiss": "閉じる",
|
||||
|
@ -31807,9 +31805,6 @@
|
|||
"xpack.observability.alertDetailContextualInsights.InsightButtonLabel": "このアラートを理解できるように支援してください",
|
||||
"xpack.observability.alertDetails.actionsButtonLabel": "アクション",
|
||||
"xpack.observability.alertDetails.addToCase": "ケースに追加",
|
||||
"xpack.observability.alertDetails.alertSummaryField.rule": "ルール",
|
||||
"xpack.observability.alertDetails.alertSummaryField.source": "送信元",
|
||||
"xpack.observability.alertDetails.alertSummaryField.tags": "タグ",
|
||||
"xpack.observability.alertDetails.editRule": "ルールを編集",
|
||||
"xpack.observability.alertDetails.editSnoozeRule": "ルールをスヌーズ",
|
||||
"xpack.observability.alertDetails.errorPromptBody": "アラート詳細の読み込みエラーが発生しました。",
|
||||
|
@ -40973,7 +40968,6 @@
|
|||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInHours": "過去{duration}時間",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInMinutes": "過去{duration}分",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInSeconds": "過去{duration}秒",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.summaryField.slo": "SLO",
|
||||
"xpack.slo.burnRateRule.name": "{name}バーンレートルール",
|
||||
"xpack.slo.burnRateRuleEditor.h5.chooseASLOToMonitorLabel": "監視するSLOを選択",
|
||||
"xpack.slo.burnRateRuleEditor.h5.defineMultipleBurnRateLabel": "複数のバーンレート時間枠を定義",
|
||||
|
|
|
@ -10545,8 +10545,6 @@
|
|||
"xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint": "在其中部署此服务的环境的名称,如“生产”或“暂存”。在 APM UI 中,您可以通过环境在全局级别轻松筛选数据。跨代理命名环境时,保持一致至关重要。",
|
||||
"xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "服务名称是 APM UI 中的初级筛选,用于分组错误并跟踪数据。允许使用的字符包括 a-z、A-Z、0-9、-、_ 和空格。",
|
||||
"xpack.apm.onboarding.specProvider.longDescription": "应用程序性能监测 (APM) 从您的应用程序内收集深入全面的性能指标和错误。其允许您实时监测数以千计的应用程序的性能。{learnMoreLink}。",
|
||||
"xpack.apm.pages.alertDetails.alertSummary.actualValue": "实际值",
|
||||
"xpack.apm.pages.alertDetails.alertSummary.expectedValue": "预期值",
|
||||
"xpack.apm.percentOfParent": "({parentType, select, transaction {事务} trace {追溯} other {parentType 未知}}的 {value})",
|
||||
"xpack.apm.profiling.callout.description": "Universal Profiling 为所有应用程序的运行时行为提供了前所未有的代码可见性。它会剖析运行服务的主机上的每一行代码,不仅包括您的应用程序代码,而且包括内核和第三方库。",
|
||||
"xpack.apm.profiling.callout.dismiss": "关闭",
|
||||
|
@ -31850,9 +31848,6 @@
|
|||
"xpack.observability.alertDetailContextualInsights.InsightButtonLabel": "帮助我了解此告警",
|
||||
"xpack.observability.alertDetails.actionsButtonLabel": "操作",
|
||||
"xpack.observability.alertDetails.addToCase": "添加到案例",
|
||||
"xpack.observability.alertDetails.alertSummaryField.rule": "规则",
|
||||
"xpack.observability.alertDetails.alertSummaryField.source": "源",
|
||||
"xpack.observability.alertDetails.alertSummaryField.tags": "标签",
|
||||
"xpack.observability.alertDetails.editRule": "编辑规则",
|
||||
"xpack.observability.alertDetails.editSnoozeRule": "暂停规则",
|
||||
"xpack.observability.alertDetails.errorPromptBody": "加载告警详情时出错。",
|
||||
|
@ -41019,7 +41014,6 @@
|
|||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInHours": "过去 {duration} 小时",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInMinutes": "过去 {duration} 分钟",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.lastDurationInSeconds": "过去 {duration} 秒",
|
||||
"xpack.slo.burnRateRule.alertDetailsAppSection.summaryField.slo": "SLO",
|
||||
"xpack.slo.burnRateRule.name": "{name} 消耗速度规则",
|
||||
"xpack.slo.burnRateRuleEditor.h5.chooseASLOToMonitorLabel": "选择要监测的 SLO",
|
||||
"xpack.slo.burnRateRuleEditor.h5.defineMultipleBurnRateLabel": "定义多个消耗速度窗口",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue