[Custom Threshold Rule] Align condition delete buttons (#171338)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2023-11-22 19:56:59 +01:00 committed by GitHub
parent 78d82bd801
commit 1cb78ec4b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 169 additions and 163 deletions

View file

@ -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 }}
/>
);
}

View file

@ -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>
);
}

View file

@ -18,6 +18,7 @@ describe('ExpressionRow', () => {
async function setup(expression: MetricExpression) {
const wrapper = mountWithIntl(
<ExpressionRow
title={<>Condition</>}
canDelete={false}
fields={[
{

View file

@ -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'} />
</>
);
};

View file

@ -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}