mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Custom Threshold Rule] Align condition delete buttons (#171338)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
78d82bd801
commit
1cb78ec4b2
5 changed files with 169 additions and 163 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiFlexItem, EuiButtonIcon } from '@elastic/eui';
|
||||
import { EuiButtonIcon } from '@elastic/eui';
|
||||
import { DELETE_LABEL } from '../../i18n_strings';
|
||||
|
||||
interface MetricRowControlProps {
|
||||
|
@ -15,19 +15,16 @@ interface MetricRowControlProps {
|
|||
|
||||
export function MetricRowControls({ onDelete, disableDelete }: MetricRowControlProps) {
|
||||
return (
|
||||
<>
|
||||
<EuiFlexItem grow={0}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="o11yMetricRowControlsButton"
|
||||
aria-label={DELETE_LABEL}
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
style={{ marginBottom: '0.6em' }}
|
||||
onClick={onDelete}
|
||||
disabled={disableDelete}
|
||||
title={DELETE_LABEL}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="o11yMetricRowControlsButton"
|
||||
aria-label={DELETE_LABEL}
|
||||
iconType="cross"
|
||||
color="text"
|
||||
onClick={onDelete}
|
||||
disabled={disableDelete}
|
||||
title={DELETE_LABEL}
|
||||
size="xs"
|
||||
css={{ height: 16 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -112,117 +112,123 @@ export function MetricRowWithAgg({
|
|||
const isFieldInvalid = get(errors, ['metrics', name, 'field']) != null || !field;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd">
|
||||
<EuiFlexItem grow>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel',
|
||||
{ defaultMessage: 'Aggregation {name}', values: { name } }
|
||||
)}
|
||||
>
|
||||
<EuiExpression
|
||||
data-test-subj="aggregationName"
|
||||
description={aggregationTypes[aggType].text}
|
||||
value={
|
||||
aggType === Aggregators.COUNT ? filter || DEFAULT_COUNT_FILTER_TITLE : field
|
||||
}
|
||||
isActive={aggTypePopoverOpen}
|
||||
display="columns"
|
||||
onClick={() => {
|
||||
setAggTypePopoverOpen(true);
|
||||
}}
|
||||
isInvalid={aggType !== Aggregators.COUNT && !field}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
}
|
||||
isOpen={aggTypePopoverOpen}
|
||||
closePopover={() => {
|
||||
setAggTypePopoverOpen(false);
|
||||
}}
|
||||
display="block"
|
||||
ownFocus
|
||||
anchorPosition={'downLeft'}
|
||||
repositionOnScroll
|
||||
>
|
||||
<div>
|
||||
<ClosablePopoverTitle onClose={() => setAggTypePopoverOpen(false)}>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel"
|
||||
defaultMessage="Aggregation {name}"
|
||||
values={{ name }}
|
||||
/>
|
||||
</ClosablePopoverTitle>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd">
|
||||
<EuiFlexItem grow>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel',
|
||||
{ defaultMessage: 'Aggregation {name}', values: { name } }
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!disableDelete && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<MetricRowControls onDelete={handleDelete} disableDelete={disableDelete} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiExpression
|
||||
data-test-subj="aggregationName"
|
||||
description={aggregationTypes[aggType].text}
|
||||
value={aggType === Aggregators.COUNT ? filter || DEFAULT_COUNT_FILTER_TITLE : field}
|
||||
isActive={aggTypePopoverOpen}
|
||||
display="columns"
|
||||
onClick={() => {
|
||||
setAggTypePopoverOpen(true);
|
||||
}}
|
||||
isInvalid={aggType !== Aggregators.COUNT && !field}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
}
|
||||
isOpen={aggTypePopoverOpen}
|
||||
closePopover={() => {
|
||||
setAggTypePopoverOpen(false);
|
||||
}}
|
||||
display="block"
|
||||
ownFocus
|
||||
anchorPosition={'downLeft'}
|
||||
repositionOnScroll
|
||||
>
|
||||
<div>
|
||||
<ClosablePopoverTitle onClose={() => setAggTypePopoverOpen(false)}>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel"
|
||||
defaultMessage="Aggregation {name}"
|
||||
values={{ name }}
|
||||
/>
|
||||
</ClosablePopoverTitle>
|
||||
|
||||
<EuiFlexGroup gutterSize="l" alignItems="flexEnd">
|
||||
<EuiFlexItem grow>
|
||||
<EuiFlexGroup gutterSize="l" alignItems="flexEnd">
|
||||
<EuiFlexItem grow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationType',
|
||||
{ defaultMessage: 'Aggregation type' }
|
||||
)}
|
||||
>
|
||||
<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,
|
||||
};
|
||||
})}
|
||||
isInvalid={isAggInvalid}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ minWidth: 300 }}>
|
||||
{aggType === Aggregators.COUNT ? (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationType',
|
||||
{ defaultMessage: 'Aggregation type' }
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.filterLabel',
|
||||
{ defaultMessage: 'KQL Filter {name}', values: { name } }
|
||||
)}
|
||||
>
|
||||
<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,
|
||||
};
|
||||
})}
|
||||
isInvalid={isAggInvalid}
|
||||
<RuleFlyoutKueryBar
|
||||
placeholder={' '}
|
||||
derivedIndexPattern={dataView}
|
||||
onChange={handleFilterChange}
|
||||
onSubmit={handleFilterChange}
|
||||
value={filter}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ minWidth: 300 }}>
|
||||
{aggType === Aggregators.COUNT ? (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.filterLabel',
|
||||
{ defaultMessage: 'KQL Filter {name}', values: { name } }
|
||||
)}
|
||||
>
|
||||
<RuleFlyoutKueryBar
|
||||
placeholder={' '}
|
||||
derivedIndexPattern={dataView}
|
||||
onChange={handleFilterChange}
|
||||
onSubmit={handleFilterChange}
|
||||
value={filter}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.fieldLabel',
|
||||
{ defaultMessage: 'Field name' }
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
) : (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.fieldLabel',
|
||||
{ defaultMessage: 'Field name' }
|
||||
)}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
isInvalid={isFieldInvalid}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={fieldOptions}
|
||||
selectedOptions={field ? [{ label: field }] : []}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ describe('ExpressionRow', () => {
|
|||
async function setup(expression: MetricExpression) {
|
||||
const wrapper = mountWithIntl(
|
||||
<ExpressionRow
|
||||
title={<>Condition</>}
|
||||
canDelete={false}
|
||||
fields={[
|
||||
{
|
||||
|
|
|
@ -12,9 +12,10 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState, ReactElement } from 'react';
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import {
|
||||
AggregationType,
|
||||
|
@ -47,6 +48,7 @@ const customComparators = {
|
|||
};
|
||||
|
||||
interface ExpressionRowProps {
|
||||
title: ReactElement;
|
||||
fields: DataViewFieldBase[];
|
||||
expressionId: number;
|
||||
expression: MetricExpression;
|
||||
|
@ -77,6 +79,7 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
remove,
|
||||
fields,
|
||||
canDelete,
|
||||
title,
|
||||
} = props;
|
||||
|
||||
const { metrics, comparator = Comparator.GT, threshold = [] } = expression;
|
||||
|
@ -146,6 +149,29 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
);
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow>
|
||||
<EuiTitle size="xs">
|
||||
<h5>{title}</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{canDelete && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="o11yExpressionRowButton"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.removeCondition',
|
||||
{
|
||||
defaultMessage: 'Remove condition',
|
||||
}
|
||||
)}
|
||||
color={'text'}
|
||||
iconType={'trash'}
|
||||
onClick={() => remove(expressionId)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow>
|
||||
<StyledExpressionRow style={{ gap: 24 }} />
|
||||
|
@ -178,25 +204,8 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
<EuiSpacer size="s" />
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
{canDelete && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="o11yExpressionRowButton"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.customThreshold.rule.alertFlyout.removeCondition',
|
||||
{
|
||||
defaultMessage: 'Remove condition',
|
||||
}
|
||||
)}
|
||||
color={'danger'}
|
||||
iconType={'trash'}
|
||||
onClick={() => remove(expressionId)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{children}
|
||||
<EuiSpacer size={'s'} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
EuiEmptyPrompt,
|
||||
EuiFormErrorText,
|
||||
EuiFormRow,
|
||||
EuiHorizontalRule,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
|
@ -202,11 +203,8 @@ export default function Expressions(props: Props) {
|
|||
|
||||
const removeExpression = useCallback(
|
||||
(id: number) => {
|
||||
const ruleCriteria = ruleParams.criteria?.slice() || [];
|
||||
if (ruleCriteria.length > 1) {
|
||||
ruleCriteria.splice(id, 1);
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
}
|
||||
const ruleCriteria = ruleParams.criteria?.filter((_, index) => index !== id) || [];
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
},
|
||||
[setRuleParams, ruleParams.criteria]
|
||||
);
|
||||
|
@ -375,30 +373,11 @@ export default function Expressions(props: Props) {
|
|||
</EuiFormErrorText>
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.customThreshold.rule.alertFlyout.setConditions"
|
||||
defaultMessage="Set rule conditions"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
{ruleParams.criteria &&
|
||||
ruleParams.criteria.map((e, idx) => {
|
||||
return (
|
||||
<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.customThreshold.rule.alertFlyout.condition"
|
||||
defaultMessage="Condition {conditionNumber}"
|
||||
values={{ conditionNumber: idx + 1 }}
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
)}
|
||||
{idx > 0 && <EuiHorizontalRule margin="s" />}
|
||||
<ExpressionRow
|
||||
canDelete={(ruleParams.criteria && ruleParams.criteria.length > 1) || false}
|
||||
fields={derivedIndexPattern.fields}
|
||||
|
@ -410,6 +389,20 @@ export default function Expressions(props: Props) {
|
|||
errors={(errors[idx] as IErrorObject) || emptyError}
|
||||
expression={e || {}}
|
||||
dataView={derivedIndexPattern}
|
||||
title={
|
||||
ruleParams.criteria.length === 1 ? (
|
||||
<FormattedMessage
|
||||
id="xpack.observability.customThreshold.rule.alertFlyout.setConditions"
|
||||
defaultMessage="Set rule conditions"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.observability.customThreshold.rule.alertFlyout.condition"
|
||||
defaultMessage="Condition {conditionNumber}"
|
||||
values={{ conditionNumber: idx + 1 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<PreviewChart
|
||||
metricExpression={e}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue