mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[AO] Update the design of Threshold rule creation form (#163313)
## Summary
Fixes #162768
Fixes #162544
### After update
<img width="466" alt="Screenshot 2023-08-09 at 17 53 44"
src="926f0c9e
-ca55-4711-be3a-2da39726caa8">
This commit is contained in:
parent
1e7efae56a
commit
2093a1fee3
14 changed files with 405 additions and 466 deletions
|
@ -1,23 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ExpressionRow should render a helpText for the of expression 1`] = `
|
||||
<FormattedMessage
|
||||
defaultMessage="Can't find a metric? {documentationLink}."
|
||||
id="xpack.observability.threshold.rule.alertFlyout.ofExpression.helpTextDetail"
|
||||
values={
|
||||
Object {
|
||||
"documentationLink": <EuiLink
|
||||
data-test-subj="thresholdRuleExpressionRowLearnHowToAddMoreDataLink"
|
||||
href="https://www.elastic.co/guide/en/observability/current/configure-settings.html"
|
||||
target="BLANK"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Learn how to add more data"
|
||||
id="xpack.observability.threshold.rule.alertFlyout.ofExpression.popoverLinkLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 * as React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { ClosablePopoverTitle } from './closable_popover_title';
|
||||
|
||||
describe('closable popover title', () => {
|
||||
it('renders with defined options', () => {
|
||||
const onClose = jest.fn();
|
||||
const children = <div className="foo" />;
|
||||
const wrapper = mount(
|
||||
<ClosablePopoverTitle onClose={onClose}>{children}</ClosablePopoverTitle>
|
||||
);
|
||||
expect(wrapper.contains(<div className="foo" />)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('onClose function gets called', () => {
|
||||
const onClose = jest.fn();
|
||||
const children = <div className="foo" />;
|
||||
const wrapper = mount(
|
||||
<ClosablePopoverTitle onClose={onClose}>{children}</ClosablePopoverTitle>
|
||||
);
|
||||
wrapper.find('EuiButtonIcon').simulate('click');
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { EuiPopoverTitle, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui';
|
||||
|
||||
interface ClosablePopoverTitleProps {
|
||||
children: JSX.Element;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function ClosablePopoverTitle({ children, onClose }: ClosablePopoverTitleProps) {
|
||||
return (
|
||||
<EuiPopoverTitle>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>{children}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType="cross"
|
||||
color="danger"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.thresholdRule.closablePopoverTitle.closeLabel',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
)}
|
||||
onClick={() => onClose()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPopoverTitle>
|
||||
);
|
||||
}
|
|
@ -11,12 +11,15 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiExpression,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { omit, range, first, xor, debounce } from 'lodash';
|
||||
import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/threshold_rule/metrics_explorer';
|
||||
import {
|
||||
Aggregators,
|
||||
|
@ -27,13 +30,8 @@ import {
|
|||
import { MetricExpression } from '../../types';
|
||||
import { CustomMetrics, AggregationTypes, NormalizedFields } from './types';
|
||||
import { MetricRowWithAgg } from './metric_row_with_agg';
|
||||
import { MetricRowWithCount } from './metric_row_with_count';
|
||||
import {
|
||||
CUSTOM_EQUATION,
|
||||
EQUATION_HELP_MESSAGE,
|
||||
LABEL_HELP_MESSAGE,
|
||||
LABEL_LABEL,
|
||||
} from '../../i18n_strings';
|
||||
import { ClosablePopoverTitle } from '../closable_popover_title';
|
||||
import { EQUATION_HELP_MESSAGE } from '../../i18n_strings';
|
||||
|
||||
export interface CustomEquationEditorProps {
|
||||
onChange: (expression: MetricExpression) => void;
|
||||
|
@ -61,7 +59,7 @@ export function CustomEquationEditor({
|
|||
const [customMetrics, setCustomMetrics] = useState<CustomMetrics>(
|
||||
expression?.customMetrics ?? [NEW_METRIC]
|
||||
);
|
||||
const [label, setLabel] = useState<string | undefined>(expression?.label || undefined);
|
||||
const [customEqPopoverOpen, setCustomEqPopoverOpen] = useState(false);
|
||||
const [equation, setEquation] = useState<string | undefined>(expression?.equation || undefined);
|
||||
const debouncedOnChange = useMemo(() => debounce(onChange, 500), [onChange]);
|
||||
|
||||
|
@ -70,48 +68,40 @@ export function CustomEquationEditor({
|
|||
const currentVars = previous?.map((m) => m.name) ?? [];
|
||||
const name = first(xor(VAR_NAMES, currentVars))!;
|
||||
const nextMetrics = [...(previous || []), { ...NEW_METRIC, name }];
|
||||
debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation, label });
|
||||
debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation });
|
||||
return nextMetrics;
|
||||
});
|
||||
}, [debouncedOnChange, equation, expression, label]);
|
||||
}, [debouncedOnChange, equation, expression]);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
(name: string) => {
|
||||
setCustomMetrics((previous) => {
|
||||
const nextMetrics = previous?.filter((row) => row.name !== name) ?? [NEW_METRIC];
|
||||
const finalMetrics = (nextMetrics.length && nextMetrics) || [NEW_METRIC];
|
||||
debouncedOnChange({ ...expression, customMetrics: finalMetrics, equation, label });
|
||||
debouncedOnChange({ ...expression, customMetrics: finalMetrics, equation });
|
||||
return finalMetrics;
|
||||
});
|
||||
},
|
||||
[equation, expression, debouncedOnChange, label]
|
||||
[equation, expression, debouncedOnChange]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(metric: MetricExpressionCustomMetric) => {
|
||||
setCustomMetrics((previous) => {
|
||||
const nextMetrics = previous?.map((m) => (m.name === metric.name ? metric : m));
|
||||
debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation, label });
|
||||
debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation });
|
||||
return nextMetrics;
|
||||
});
|
||||
},
|
||||
[equation, expression, debouncedOnChange, label]
|
||||
[equation, expression, debouncedOnChange]
|
||||
);
|
||||
|
||||
const handleEquationChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEquation(e.target.value);
|
||||
debouncedOnChange({ ...expression, customMetrics, equation: e.target.value, label });
|
||||
debouncedOnChange({ ...expression, customMetrics, equation: e.target.value });
|
||||
},
|
||||
[debouncedOnChange, expression, customMetrics, label]
|
||||
);
|
||||
|
||||
const handleLabelChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setLabel(e.target.value);
|
||||
debouncedOnChange({ ...expression, customMetrics, equation, label: e.target.value });
|
||||
},
|
||||
[debouncedOnChange, expression, customMetrics, equation]
|
||||
[debouncedOnChange, expression, customMetrics]
|
||||
);
|
||||
|
||||
const disableAdd = customMetrics?.length === MAX_VARIABLES;
|
||||
|
@ -119,42 +109,24 @@ export function CustomEquationEditor({
|
|||
|
||||
const filteredAggregationTypes = omit(aggregationTypes, OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS);
|
||||
|
||||
const metricRows = customMetrics?.map((row) => {
|
||||
if (row.aggType === Aggregators.COUNT) {
|
||||
return (
|
||||
<MetricRowWithCount
|
||||
key={row.name}
|
||||
name={row.name}
|
||||
agg={row.aggType}
|
||||
filter={row.filter}
|
||||
onAdd={handleAddNewRow}
|
||||
onDelete={handleDelete}
|
||||
disableAdd={disableAdd}
|
||||
aggregationTypes={filteredAggregationTypes}
|
||||
disableDelete={disableDelete}
|
||||
onChange={handleChange}
|
||||
errors={errors}
|
||||
dataView={dataView}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MetricRowWithAgg
|
||||
key={row.name}
|
||||
name={row.name}
|
||||
aggType={row.aggType}
|
||||
aggregationTypes={filteredAggregationTypes}
|
||||
field={row.field}
|
||||
fields={fields}
|
||||
onAdd={handleAddNewRow}
|
||||
onDelete={handleDelete}
|
||||
disableAdd={disableAdd}
|
||||
disableDelete={disableDelete}
|
||||
onChange={handleChange}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const metricRows = customMetrics?.map((row) => (
|
||||
<MetricRowWithAgg
|
||||
key={row.name}
|
||||
name={row.name}
|
||||
aggType={row.aggType}
|
||||
aggregationTypes={filteredAggregationTypes}
|
||||
field={row.field}
|
||||
filter={row.filter}
|
||||
fields={fields}
|
||||
onAdd={handleAddNewRow}
|
||||
onDelete={handleDelete}
|
||||
disableAdd={disableAdd}
|
||||
disableDelete={disableDelete}
|
||||
onChange={handleChange}
|
||||
errors={errors}
|
||||
dataView={dataView}
|
||||
/>
|
||||
));
|
||||
|
||||
const placeholder = useMemo(() => {
|
||||
return customMetrics?.map((row) => row.name).join(' + ');
|
||||
|
@ -181,42 +153,69 @@ export function CustomEquationEditor({
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label="Equation"
|
||||
fullWidth
|
||||
helpText={EQUATION_HELP_MESSAGE}
|
||||
isInvalid={errors.equation != null}
|
||||
error={[errors.equation]}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="thresholdRuleCustomEquationEditorFieldText"
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationAndThreshold',
|
||||
{ defaultMessage: 'Equation and threshold' }
|
||||
)}
|
||||
error={[errors.equation]}
|
||||
isInvalid={errors.equation != null}
|
||||
compressed
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiExpression
|
||||
data-test-subj="customEquation"
|
||||
description={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationLabel',
|
||||
{ defaultMessage: 'Equation' }
|
||||
)}
|
||||
value={equation ?? placeholder}
|
||||
display={'columns'}
|
||||
onClick={() => {
|
||||
setCustomEqPopoverOpen(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
}
|
||||
isOpen={customEqPopoverOpen}
|
||||
closePopover={() => {
|
||||
setCustomEqPopoverOpen(false);
|
||||
}}
|
||||
display="block"
|
||||
ownFocus
|
||||
anchorPosition={'downLeft'}
|
||||
repositionOnScroll
|
||||
>
|
||||
<div>
|
||||
<ClosablePopoverTitle onClose={() => setCustomEqPopoverOpen(false)}>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.threshold.rule.alertFlyout.customEquationLabel"
|
||||
defaultMessage="Custom equation"
|
||||
/>
|
||||
</ClosablePopoverTitle>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
placeholder={placeholder}
|
||||
onChange={handleEquationChange}
|
||||
value={equation ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow label={LABEL_LABEL} fullWidth helpText={LABEL_HELP_MESSAGE}>
|
||||
<EuiFieldText
|
||||
data-test-subj="thresholdRuleCustomEquationEditorFieldText"
|
||||
compressed
|
||||
fullWidth
|
||||
value={label}
|
||||
placeholder={CUSTOM_EQUATION}
|
||||
onChange={handleLabelChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
helpText={EQUATION_HELP_MESSAGE}
|
||||
isInvalid={errors.equation != null}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="thresholdRuleCustomEquationEditorFieldText"
|
||||
isInvalid={errors.equation != null}
|
||||
compressed
|
||||
fullWidth
|
||||
placeholder={placeholder}
|
||||
onChange={handleEquationChange}
|
||||
value={equation ?? ''}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export function MetricRowControls({ onDelete, disableDelete }: MetricRowControlP
|
|||
aria-label={DELETE_LABEL}
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
style={{ marginBottom: '0.2em' }}
|
||||
style={{ marginBottom: '0.6em' }}
|
||||
onClick={onDelete}
|
||||
disabled={disableDelete}
|
||||
title={DELETE_LABEL}
|
||||
|
|
|
@ -7,24 +7,31 @@
|
|||
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiHorizontalRule,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiSelect,
|
||||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiPopover,
|
||||
EuiExpression,
|
||||
} from '@elastic/eui';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import React, { useMemo, useCallback, useState } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ValidNormalizedTypes } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { Aggregators, CustomMetricAggTypes } from '../../../../../common/threshold_rule/types';
|
||||
import { MetricRowControls } from './metric_row_controls';
|
||||
import { NormalizedFields, MetricRowBaseProps } from './types';
|
||||
import { ClosablePopoverTitle } from '../closable_popover_title';
|
||||
import { MetricsExplorerKueryBar } from '../kuery_bar';
|
||||
|
||||
interface MetricRowWithAggProps extends MetricRowBaseProps {
|
||||
aggType?: CustomMetricAggTypes;
|
||||
field?: string;
|
||||
dataView: DataViewBase;
|
||||
filter?: string;
|
||||
fields: NormalizedFields;
|
||||
}
|
||||
|
||||
|
@ -33,6 +40,8 @@ export function MetricRowWithAgg({
|
|||
aggType = Aggregators.AVERAGE,
|
||||
field,
|
||||
onDelete,
|
||||
dataView,
|
||||
filter,
|
||||
disableDelete,
|
||||
fields,
|
||||
aggregationTypes,
|
||||
|
@ -43,6 +52,8 @@ export function MetricRowWithAgg({
|
|||
onDelete(name);
|
||||
}, [name, onDelete]);
|
||||
|
||||
const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false);
|
||||
|
||||
const fieldOptions = useMemo(
|
||||
() =>
|
||||
fields.reduce((acc, fieldValue) => {
|
||||
|
@ -59,15 +70,6 @@ export function MetricRowWithAgg({
|
|||
[fields, aggregationTypes, aggType]
|
||||
);
|
||||
|
||||
const aggOptions = useMemo(
|
||||
() =>
|
||||
Object.values(aggregationTypes).map((a) => ({
|
||||
text: a.text,
|
||||
value: a.value,
|
||||
})),
|
||||
[aggregationTypes]
|
||||
);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(selectedOptions: EuiComboBoxOptionOption[]) => {
|
||||
onChange({
|
||||
|
@ -80,62 +82,141 @@ export function MetricRowWithAgg({
|
|||
);
|
||||
|
||||
const handleAggChange = useCallback(
|
||||
(el: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
(customAggType: string) => {
|
||||
onChange({
|
||||
name,
|
||||
field,
|
||||
aggType: el.target.value as CustomMetricAggTypes,
|
||||
aggType: customAggType as CustomMetricAggTypes,
|
||||
});
|
||||
},
|
||||
[name, field, onChange]
|
||||
);
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(filterString: string) => {
|
||||
onChange({
|
||||
name,
|
||||
filter: filterString,
|
||||
aggType,
|
||||
});
|
||||
},
|
||||
[name, aggType, onChange]
|
||||
);
|
||||
|
||||
const isAggInvalid = get(errors, ['customMetrics', name, 'aggType']) != null;
|
||||
const isFieldInvalid = get(errors, ['customMetrics', name, 'field']) != null || !field;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd">
|
||||
<EuiFlexItem style={{ maxWidth: 145 }}>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel',
|
||||
{ defaultMessage: 'Aggregation {name}', values: { name } }
|
||||
)}
|
||||
isInvalid={isAggInvalid}
|
||||
<EuiFlexItem grow>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel',
|
||||
{ defaultMessage: 'Aggregation {name}', values: { name } }
|
||||
)}
|
||||
isInvalid={aggType !== Aggregators.COUNT && !field}
|
||||
>
|
||||
<EuiExpression
|
||||
data-test-subj="aggregationName"
|
||||
description={aggregationTypes[aggType].text}
|
||||
value={aggType === Aggregators.COUNT ? filter : field}
|
||||
isActive={aggTypePopoverOpen}
|
||||
display={'columns'}
|
||||
onClick={() => {
|
||||
setAggTypePopoverOpen(true);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
}
|
||||
isOpen={aggTypePopoverOpen}
|
||||
closePopover={() => {
|
||||
setAggTypePopoverOpen(false);
|
||||
}}
|
||||
display="block"
|
||||
ownFocus
|
||||
anchorPosition={'downLeft'}
|
||||
repositionOnScroll
|
||||
>
|
||||
<EuiSelect
|
||||
data-test-subj="thresholdRuleMetricRowWithAggSelect"
|
||||
compressed
|
||||
options={aggOptions}
|
||||
value={aggType}
|
||||
isInvalid={isAggInvalid}
|
||||
onChange={handleAggChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.fieldLabel',
|
||||
{ defaultMessage: 'Field {name}', values: { name } }
|
||||
)}
|
||||
isInvalid={isFieldInvalid}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
compressed
|
||||
isInvalid={isFieldInvalid}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={fieldOptions}
|
||||
selectedOptions={field ? [{ label: field }] : []}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<div>
|
||||
<ClosablePopoverTitle onClose={() => setAggTypePopoverOpen(false)}>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel"
|
||||
defaultMessage="Aggregation {name}"
|
||||
values={{ name }}
|
||||
/>
|
||||
</ClosablePopoverTitle>
|
||||
|
||||
<EuiFlexGroup gutterSize="l" alignItems="flexEnd">
|
||||
<EuiFlexItem grow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationType',
|
||||
{ defaultMessage: 'Aggregation type' }
|
||||
)}
|
||||
isInvalid={isAggInvalid}
|
||||
>
|
||||
<EuiSelect
|
||||
data-test-subj="aggregationTypeSelect"
|
||||
id="aggTypeField"
|
||||
value={aggType}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
handleAggChange(e.target.value);
|
||||
}}
|
||||
options={Object.values(aggregationTypes).map(({ text, value }) => {
|
||||
return {
|
||||
text,
|
||||
value,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ minWidth: 300 }}>
|
||||
{aggType === Aggregators.COUNT ? (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel',
|
||||
{ defaultMessage: 'KQL Filter {name}', values: { name } }
|
||||
)}
|
||||
>
|
||||
<MetricsExplorerKueryBar
|
||||
placeholder={' '}
|
||||
derivedIndexPattern={dataView}
|
||||
onChange={handleFilterChange}
|
||||
onSubmit={handleFilterChange}
|
||||
value={filter}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.fieldLabel',
|
||||
{ defaultMessage: 'Field name' }
|
||||
)}
|
||||
isInvalid={isFieldInvalid}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
isInvalid={isFieldInvalid}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={fieldOptions}
|
||||
selectedOptions={field ? [{ label: field }] : []}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
<MetricRowControls onDelete={handleDelete} disableDelete={disableDelete} />
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,110 +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 { EuiFormRow, EuiHorizontalRule, EuiFlexItem, EuiFlexGroup, EuiSelect } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { Aggregators, CustomMetricAggTypes } from '../../../../../common/threshold_rule/types';
|
||||
import { MetricRowControls } from './metric_row_controls';
|
||||
import { MetricRowBaseProps } from './types';
|
||||
import { MetricsExplorerKueryBar } from '../kuery_bar';
|
||||
|
||||
interface MetricRowWithCountProps extends MetricRowBaseProps {
|
||||
agg?: Aggregators;
|
||||
filter?: string;
|
||||
dataView: DataViewBase;
|
||||
}
|
||||
|
||||
export function MetricRowWithCount({
|
||||
name,
|
||||
agg,
|
||||
filter,
|
||||
onDelete,
|
||||
disableDelete,
|
||||
onChange,
|
||||
aggregationTypes,
|
||||
dataView,
|
||||
}: MetricRowWithCountProps) {
|
||||
const aggOptions = useMemo(
|
||||
() =>
|
||||
Object.values(aggregationTypes)
|
||||
.filter((aggType) => aggType.value !== Aggregators.CUSTOM)
|
||||
.map((aggType) => ({
|
||||
text: aggType.text,
|
||||
value: aggType.value,
|
||||
})),
|
||||
[aggregationTypes]
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
onDelete(name);
|
||||
}, [name, onDelete]);
|
||||
|
||||
const handleAggChange = useCallback(
|
||||
(el: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
onChange({
|
||||
name,
|
||||
filter,
|
||||
aggType: el.target.value as CustomMetricAggTypes,
|
||||
});
|
||||
},
|
||||
[name, filter, onChange]
|
||||
);
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(filterString: string) => {
|
||||
onChange({
|
||||
name,
|
||||
filter: filterString,
|
||||
aggType: agg as CustomMetricAggTypes,
|
||||
});
|
||||
},
|
||||
[name, agg, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd">
|
||||
<EuiFlexItem style={{ maxWidth: 145 }}>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel',
|
||||
{ defaultMessage: 'Aggregation {name}', values: { name } }
|
||||
)}
|
||||
>
|
||||
<EuiSelect
|
||||
data-test-subj="thresholdRuleMetricRowWithCountSelect"
|
||||
compressed
|
||||
options={aggOptions}
|
||||
value={agg}
|
||||
onChange={handleAggChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel',
|
||||
{ defaultMessage: 'KQL Filter {name}', values: { name } }
|
||||
)}
|
||||
>
|
||||
<MetricsExplorerKueryBar
|
||||
placeholder={' '}
|
||||
compressed
|
||||
derivedIndexPattern={dataView}
|
||||
onChange={handleFilterChange}
|
||||
onSubmit={handleFilterChange}
|
||||
value={filter}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<MetricRowControls onDelete={handleDelete} disableDelete={disableDelete} />
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -67,15 +67,16 @@ describe('ExpressionRow', () => {
|
|||
threshold: [0.5],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
aggType: 'avg',
|
||||
aggType: 'custom',
|
||||
};
|
||||
const { wrapper, update } = await setup(expression as MetricExpression);
|
||||
await update();
|
||||
const [valueMatch] =
|
||||
wrapper
|
||||
.html()
|
||||
.match('<span class="euiExpression__value css-1lfq7nz-euiExpression__value">50</span>') ??
|
||||
[];
|
||||
.match(
|
||||
'<span class="euiExpression__value css-uocz3u-euiExpression__value-columns">50</span>'
|
||||
) ?? [];
|
||||
expect(valueMatch).toBeTruthy();
|
||||
});
|
||||
|
||||
|
@ -86,34 +87,15 @@ describe('ExpressionRow', () => {
|
|||
threshold: [0.5],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
aggType: 'avg',
|
||||
aggType: 'custom',
|
||||
};
|
||||
const { wrapper } = await setup(expression as MetricExpression);
|
||||
const [valueMatch] =
|
||||
wrapper
|
||||
.html()
|
||||
.match('<span class="euiExpression__value css-1lfq7nz-euiExpression__value">0.5</span>') ??
|
||||
[];
|
||||
.match(
|
||||
'<span class="euiExpression__value css-uocz3u-euiExpression__value-columns">0.5</span>'
|
||||
) ?? [];
|
||||
expect(valueMatch).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render a helpText for the of expression', async () => {
|
||||
const expression = {
|
||||
metric: 'system.load.1',
|
||||
comparator: Comparator.GT,
|
||||
threshold: [0.5],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
aggType: 'avg',
|
||||
} as MetricExpression;
|
||||
|
||||
const { wrapper } = await setup(expression as MetricExpression);
|
||||
|
||||
const helpText = wrapper
|
||||
.find('[data-test-subj="thresholdRuleOfExpression"]')
|
||||
.at(0)
|
||||
.prop('helpText');
|
||||
|
||||
expect(helpText).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,30 +6,28 @@
|
|||
*/
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiExpression,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import {
|
||||
AggregationType,
|
||||
builtInComparators,
|
||||
IErrorObject,
|
||||
OfExpression,
|
||||
ThresholdExpression,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import useToggle from 'react-use/lib/useToggle';
|
||||
import { Aggregators, Comparator } from '../../../../common/threshold_rule/types';
|
||||
import { debounce } from 'lodash';
|
||||
import { Comparator } from '../../../../common/threshold_rule/types';
|
||||
import { AGGREGATION_TYPES, DerivedIndexPattern, MetricExpression } from '../types';
|
||||
import { CustomEquationEditor } from './custom_equation';
|
||||
import { CUSTOM_EQUATION } from '../i18n_strings';
|
||||
import { CUSTOM_EQUATION, LABEL_HELP_MESSAGE, LABEL_LABEL } from '../i18n_strings';
|
||||
import { decimalToPct, pctToDecimal } from '../helpers/corrected_percent_convert';
|
||||
|
||||
const customComparators = {
|
||||
|
@ -62,14 +60,8 @@ const StyledExpressionRow = euiStyled(EuiFlexGroup)`
|
|||
margin: 0 -4px;
|
||||
`;
|
||||
|
||||
const StyledExpression = euiStyled.div`
|
||||
padding: 0 4px;
|
||||
`;
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
||||
const [isExpanded, toggle] = useToggle(true);
|
||||
|
||||
const {
|
||||
dataView,
|
||||
children,
|
||||
|
@ -82,21 +74,10 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
canDelete,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
aggType = AGGREGATION_TYPES.MAX,
|
||||
metric,
|
||||
comparator = Comparator.GT,
|
||||
threshold = [],
|
||||
} = expression;
|
||||
const { metric, comparator = Comparator.GT, threshold = [] } = expression;
|
||||
|
||||
const isMetricPct = useMemo(() => Boolean(metric && metric.endsWith('.pct')), [metric]);
|
||||
|
||||
const updateMetric = useCallback(
|
||||
(m?: MetricExpression['metric']) => {
|
||||
setRuleParams(expressionId, { ...expression, metric: m });
|
||||
},
|
||||
[expressionId, expression, setRuleParams]
|
||||
);
|
||||
const [label, setLabel] = useState<string | undefined>(expression?.label || undefined);
|
||||
|
||||
const updateComparator = useCallback(
|
||||
(c?: string) => {
|
||||
|
@ -127,6 +108,10 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
},
|
||||
[expressionId, setRuleParams]
|
||||
);
|
||||
const debouncedLabelChange = useMemo(
|
||||
() => debounce(handleCustomMetricChange, 300),
|
||||
[handleCustomMetricChange]
|
||||
);
|
||||
|
||||
const criticalThresholdExpression = (
|
||||
<ThresholdElement
|
||||
|
@ -144,89 +129,46 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
name: f.name,
|
||||
}));
|
||||
|
||||
const handleLabelChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setLabel(e.target.value);
|
||||
debouncedLabelChange({ ...expression, label: e.target.value });
|
||||
},
|
||||
[debouncedLabelChange, expression]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType={isExpanded ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={toggle}
|
||||
data-test-subj="thresholdRuleExpandRow"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.expandRowLabel',
|
||||
{
|
||||
defaultMessage: 'Expand row.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow>
|
||||
<StyledExpressionRow style={{ gap: aggType !== 'custom' ? 24 : 12 }}>
|
||||
<StyledExpression>
|
||||
<EuiExpression
|
||||
data-test-subj="thresholdRuleCustomEquationWhen"
|
||||
description={i18n.translate(
|
||||
'xpack.observability.thresholdRule.expressionItems.descriptionLabel',
|
||||
{
|
||||
defaultMessage: 'when',
|
||||
}
|
||||
)}
|
||||
value={aggregationType.custom.text}
|
||||
display={'inline'}
|
||||
/>
|
||||
</StyledExpression>
|
||||
{!['count', 'custom'].includes(aggType) && (
|
||||
<StyledExpression>
|
||||
<OfExpression
|
||||
customAggTypesOptions={aggregationType}
|
||||
aggField={metric}
|
||||
fields={normalizedFields}
|
||||
aggType={aggType}
|
||||
errors={errors}
|
||||
onChangeSelectedAggField={updateMetric}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.observability.threshold.rule.alertFlyout.ofExpression.helpTextDetail"
|
||||
defaultMessage="Can't find a metric? {documentationLink}."
|
||||
values={{
|
||||
documentationLink: (
|
||||
<EuiLink
|
||||
data-test-subj="thresholdRuleExpressionRowLearnHowToAddMoreDataLink"
|
||||
href="https://www.elastic.co/guide/en/observability/current/configure-settings.html"
|
||||
target="BLANK"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.threshold.rule.alertFlyout.ofExpression.popoverLinkLabel"
|
||||
defaultMessage="Learn how to add more data"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
data-test-subj="thresholdRuleOfExpression"
|
||||
/>
|
||||
</StyledExpression>
|
||||
)}
|
||||
<StyledExpressionRow style={{ gap: 24 }} />
|
||||
<>
|
||||
<EuiSpacer size={'xs'} />
|
||||
<CustomEquationEditor
|
||||
expression={expression}
|
||||
fields={normalizedFields}
|
||||
aggregationTypes={aggregationType}
|
||||
onChange={handleCustomMetricChange}
|
||||
errors={errors}
|
||||
dataView={dataView}
|
||||
/>
|
||||
{criticalThresholdExpression}
|
||||
</StyledExpressionRow>
|
||||
|
||||
{aggType === Aggregators.CUSTOM && (
|
||||
<>
|
||||
<EuiSpacer size={'m'} />
|
||||
<StyledExpressionRow>
|
||||
<CustomEquationEditor
|
||||
expression={expression}
|
||||
fields={normalizedFields}
|
||||
aggregationTypes={aggregationType}
|
||||
onChange={handleCustomMetricChange}
|
||||
errors={errors}
|
||||
dataView={dataView}
|
||||
/>
|
||||
</StyledExpressionRow>
|
||||
<EuiSpacer size={'s'} />
|
||||
</>
|
||||
)}
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow label={LABEL_LABEL} fullWidth helpText={LABEL_HELP_MESSAGE}>
|
||||
<EuiFieldText
|
||||
data-test-subj="thresholdRuleCustomEquationEditorFieldText"
|
||||
compressed
|
||||
fullWidth
|
||||
value={label}
|
||||
placeholder={CUSTOM_EQUATION}
|
||||
onChange={handleLabelChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
{canDelete && (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -244,7 +186,7 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{isExpanded ? <div style={{ padding: '0 0 0 28px' }}>{children}</div> : null}
|
||||
{children}
|
||||
<EuiSpacer size={'s'} />
|
||||
</>
|
||||
);
|
||||
|
@ -266,16 +208,16 @@ const ThresholdElement: React.FC<{
|
|||
|
||||
return (
|
||||
<>
|
||||
<StyledExpression>
|
||||
<ThresholdExpression
|
||||
thresholdComparator={comparator || Comparator.GT}
|
||||
threshold={displayedThreshold}
|
||||
customComparators={customComparators}
|
||||
onChangeSelectedThresholdComparator={updateComparator}
|
||||
onChangeSelectedThreshold={updateThreshold}
|
||||
errors={errors}
|
||||
/>
|
||||
</StyledExpression>
|
||||
<ThresholdExpression
|
||||
thresholdComparator={comparator || Comparator.GT}
|
||||
threshold={displayedThreshold}
|
||||
customComparators={customComparators}
|
||||
onChangeSelectedThresholdComparator={updateComparator}
|
||||
onChangeSelectedThreshold={updateThreshold}
|
||||
errors={errors}
|
||||
display="fullWidth"
|
||||
/>
|
||||
|
||||
{isMetricPct && (
|
||||
<div
|
||||
style={{
|
||||
|
|
|
@ -20,7 +20,7 @@ export const LABEL_LABEL = i18n.translate(
|
|||
export const LABEL_HELP_MESSAGE = i18n.translate(
|
||||
'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage',
|
||||
{
|
||||
defaultMessage: 'Custom label will show on the alert chart and in reason/alert title',
|
||||
defaultMessage: 'Custom label will show on the alert chart and in reason',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -381,42 +381,56 @@ export default function Expressions(props: Props) {
|
|||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{ruleParams.criteria &&
|
||||
ruleParams.criteria.map((e, idx) => {
|
||||
return (
|
||||
<ExpressionRow
|
||||
canDelete={(ruleParams.criteria && ruleParams.criteria.length > 1) || false}
|
||||
fields={derivedIndexPattern.fields as any}
|
||||
remove={removeExpression}
|
||||
addExpression={addExpression}
|
||||
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
||||
expressionId={idx}
|
||||
setRuleParams={updateParams}
|
||||
errors={(errors[idx] as IErrorObject) || emptyError}
|
||||
expression={e || {}}
|
||||
dataView={derivedIndexPattern}
|
||||
>
|
||||
{/* Preview */}
|
||||
<ExpressionChart
|
||||
expression={e}
|
||||
derivedIndexPattern={derivedIndexPattern}
|
||||
filterQuery={ruleParams.filterQuery}
|
||||
groupBy={ruleParams.groupBy}
|
||||
timeFieldName={dataView?.timeFieldName}
|
||||
/>
|
||||
</ExpressionRow>
|
||||
<div key={idx}>
|
||||
{/* index has semantic meaning, we show the condition title starting from the 2nd one */}
|
||||
{idx >= 1 && (
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.threshold.rule.alertFlyout.condition"
|
||||
defaultMessage="Condition {conditionNumber}"
|
||||
values={{ conditionNumber: idx + 1 }}
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
)}
|
||||
<ExpressionRow
|
||||
canDelete={(ruleParams.criteria && ruleParams.criteria.length > 1) || false}
|
||||
fields={derivedIndexPattern.fields as any}
|
||||
remove={removeExpression}
|
||||
addExpression={addExpression}
|
||||
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
||||
expressionId={idx}
|
||||
setRuleParams={updateParams}
|
||||
errors={(errors[idx] as IErrorObject) || emptyError}
|
||||
expression={e || {}}
|
||||
dataView={derivedIndexPattern}
|
||||
>
|
||||
{/* Preview */}
|
||||
<ExpressionChart
|
||||
expression={e}
|
||||
derivedIndexPattern={derivedIndexPattern}
|
||||
filterQuery={ruleParams.filterQuery}
|
||||
groupBy={ruleParams.groupBy}
|
||||
timeFieldName={dataView?.timeFieldName}
|
||||
/>
|
||||
</ExpressionRow>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div style={{ marginLeft: 28 }}>
|
||||
<ForLastExpression
|
||||
timeWindowSize={timeSize}
|
||||
timeWindowUnit={timeUnit}
|
||||
errors={emptyError}
|
||||
onChangeWindowSize={updateTimeSize}
|
||||
onChangeWindowUnit={updateTimeUnit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ForLastExpression
|
||||
timeWindowSize={timeSize}
|
||||
timeWindowUnit={timeUnit}
|
||||
errors={emptyError}
|
||||
onChangeWindowSize={updateTimeSize}
|
||||
onChangeWindowUnit={updateTimeUnit}
|
||||
display="fullWidth"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<div>
|
||||
<EuiButtonEmpty
|
||||
|
|
|
@ -27294,9 +27294,7 @@
|
|||
"xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "Dernière {lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} many {ces champs} other {ces champs}}. Pour en savoir plus, consultez notre {filteringAndGroupingLink}.",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "Agrégation {name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.fieldLabel": "Champ {name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "Filtre KQL {name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.ofExpression.helpTextDetail": "Vous ne trouvez pas un indicateur ? {documentationLink}.",
|
||||
"xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "Dernière {lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "Dernières {lookback} {timeLabel} de données pour {id}",
|
||||
"xpack.observability.threshold.rule.threshold.errorAlertReason": "Elasticsearch a échoué lors de l'interrogation des données pour {metric}",
|
||||
|
@ -27809,9 +27807,7 @@
|
|||
"xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "Le seuil est requis.",
|
||||
"xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "Les seuils doivent contenir un nombre valide.",
|
||||
"xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "La taille de temps est requise.",
|
||||
"xpack.observability.threshold.rule.alertFlyout.expandRowLabel": "Développer la ligne.",
|
||||
"xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "Activez cette option pour déclencher l’action si un groupe précédemment détecté cesse de signaler des résultats. Ce n’est pas recommandé pour les infrastructures à montée en charge dynamique qui peuvent rapidement lancer ou stopper des nœuds automatiquement.",
|
||||
"xpack.observability.threshold.rule.alertFlyout.ofExpression.popoverLinkLabel": "Apprenez comment ajouter davantage de données",
|
||||
"xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "N'est pas entre",
|
||||
"xpack.observability.threshold.rule.alertFlyout.removeCondition": "Retirer la condition",
|
||||
"xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[AUCUNE DONNÉE]",
|
||||
|
@ -27851,7 +27847,6 @@
|
|||
"xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "Graphique par",
|
||||
"xpack.observability.threshold.ruleExplorer.groupByLabel": "Tout",
|
||||
"xpack.observability.threshold.ruleName": "Seuil (Version d'évaluation technique)",
|
||||
"xpack.observability.thresholdRule.expressionItems.descriptionLabel": "quand",
|
||||
"xpack.observability.uiSettings.betaLabel": "bêta",
|
||||
"xpack.observability.uiSettings.technicalPreviewLabel": "version d'évaluation technique",
|
||||
"xpack.observability.uiSettings.throttlingDocsLinkText": "lisez la notification ici.",
|
||||
|
|
|
@ -27294,9 +27294,7 @@
|
|||
"xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "最後の{lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "フィルタークエリには{groupCount, plural, other {これらのフィールド}}に対する一致が含まれているため、このルールによって、想定を下回る{matchedGroups}に関するアラートが発行される場合があります。詳細については、{filteringAndGroupingLink}を参照してください。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "アグリゲーション{name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.fieldLabel": "フィールド{name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "KQLフィルター{name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.ofExpression.helpTextDetail": "メトリックが見つからない場合は、{documentationLink}。",
|
||||
"xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "最後の{lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id}のデータの最後の{lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.threshold.errorAlertReason": "{metric}のデータのクエリを試行しているときに、Elasticsearchが失敗しました",
|
||||
|
@ -27809,9 +27807,7 @@
|
|||
"xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "しきい値が必要です。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "しきい値には有効な数値を含める必要があります。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "ページサイズが必要です。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.expandRowLabel": "行を展開します。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "以前に検出されたグループが結果を報告しなくなった場合は、これを有効にすると、アクションがトリガーされます。自動的に急速にノードを開始および停止することがある動的に拡張するインフラストラクチャーでは、これは推奨されません。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.ofExpression.popoverLinkLabel": "データの追加方法",
|
||||
"xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "is not between",
|
||||
"xpack.observability.threshold.rule.alertFlyout.removeCondition": "条件を削除",
|
||||
"xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[データなし]",
|
||||
|
@ -27851,7 +27847,6 @@
|
|||
"xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "graph/",
|
||||
"xpack.observability.threshold.ruleExplorer.groupByLabel": "すべて",
|
||||
"xpack.observability.threshold.ruleName": "しきい値(テクニカルプレビュー)",
|
||||
"xpack.observability.thresholdRule.expressionItems.descriptionLabel": "タイミング",
|
||||
"xpack.observability.uiSettings.betaLabel": "ベータ",
|
||||
"xpack.observability.uiSettings.technicalPreviewLabel": "テクニカルプレビュー",
|
||||
"xpack.observability.uiSettings.throttlingDocsLinkText": "こちらで通知をお読みください。",
|
||||
|
|
|
@ -27292,9 +27292,7 @@
|
|||
"xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "过去 {lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "聚合 {name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.fieldLabel": "字段 {name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "KQL 筛选 {name}",
|
||||
"xpack.observability.threshold.rule.alertFlyout.ofExpression.helpTextDetail": "找不到指标?{documentationLink}。",
|
||||
"xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "过去 {lookback} {timeLabel}",
|
||||
"xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id} 过去 {lookback} {timeLabel}的数据",
|
||||
"xpack.observability.threshold.rule.threshold.errorAlertReason": "Elasticsearch 尝试查询 {metric} 的数据时出现故障",
|
||||
|
@ -27807,9 +27805,7 @@
|
|||
"xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "“阈值”必填。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "阈值必须包含有效数字。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "“时间大小”必填。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.expandRowLabel": "展开行。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "启用此选项可在之前检测的组开始不报告任何数据时触发操作。不建议将此选项用于可能会快速自动启动和停止节点的动态扩展基础架构。",
|
||||
"xpack.observability.threshold.rule.alertFlyout.ofExpression.popoverLinkLabel": "了解如何添加更多数据",
|
||||
"xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "不介于",
|
||||
"xpack.observability.threshold.rule.alertFlyout.removeCondition": "删除条件",
|
||||
"xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[无数据]",
|
||||
|
@ -27849,7 +27845,6 @@
|
|||
"xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "图表绘制依据",
|
||||
"xpack.observability.threshold.ruleExplorer.groupByLabel": "所有内容",
|
||||
"xpack.observability.threshold.ruleName": "阈值(技术预览)",
|
||||
"xpack.observability.thresholdRule.expressionItems.descriptionLabel": "当",
|
||||
"xpack.observability.uiSettings.betaLabel": "公测版",
|
||||
"xpack.observability.uiSettings.technicalPreviewLabel": "技术预览",
|
||||
"xpack.observability.uiSettings.throttlingDocsLinkText": "在此处阅读通知。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue