The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/alerting/unified/components/silences/MatchersField.tsx

186 lines
7.0 KiB

import { css, cx } from '@emotion/css';
import { useEffect } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, useTranslate } from '@grafana/i18n';
import { Button, Divider, Field, IconButton, Input, Select, useStyles2 } from '@grafana/ui';
import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi';
import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types';
import { SilenceFormFields } from '../../types/silence-form';
import { matcherFieldOptions } from '../../utils/alertmanager';
interface Props {
className?: string;
required: boolean;
ruleUid?: string;
}
const MatchersField = ({ className, required, ruleUid }: Props) => {
const styles = useStyles2(getStyles);
const formApi = useFormContext<SilenceFormFields>();
const {
control,
register,
formState: { errors },
} = formApi;
const { fields: matchers = [], append, remove } = useFieldArray<SilenceFormFields>({ name: 'matchers' });
const [getAlertRule, { data: alertRule }] = alertRuleApi.endpoints.getAlertRule.useLazyQuery();
useEffect(() => {
// If we have a UID, fetch the alert rule details so we can display the rule name
if (ruleUid) {
getAlertRule({ uid: ruleUid });
}
}, [getAlertRule, ruleUid]);
const { t } = useTranslate();
return (
<div className={className}>
<Field
label={t('alerting.matchers-field.label-refine-affected-alerts', 'Refine affected alerts')}
required={required}
>
<div>
<div className={cx(styles.matchers, styles.indent)}>
{alertRule && (
<div>
<Field label={t('alerting.matchers-field.label-alert-rule', 'Alert rule')} disabled>
<Input id="alert-rule-name" defaultValue={alertRule.grafana_alert.title} disabled />
</Field>
<Divider />
</div>
)}
{matchers.map((matcher, index) => {
return (
<div className={styles.row} key={`${matcher.id}`} data-testid="matcher">
<Field
label={t('alerting.matchers-field.label-label', 'Label')}
invalid={!!errors?.matchers?.[index]?.name}
error={errors?.matchers?.[index]?.name?.message}
>
<Input
{...register(`matchers.${index}.name` as const, {
required: {
value: required,
message: t('alerting.matchers-field.message.required', 'Required.'),
},
})}
defaultValue={matcher.name}
placeholder={t('alerting.matchers-field.placeholder-label', 'label')}
id={`matcher-${index}-label`}
/>
</Field>
<Field label={t('alerting.matchers-field.label-operator', 'Operator')}>
<Controller
control={control}
render={({ field: { onChange, ref, ...field } }) => (
<Select
{...field}
onChange={(value) => onChange(value.value)}
className={styles.matcherOptions}
options={matcherFieldOptions}
aria-label={t('alerting.matchers-field.aria-label-operator', 'operator')}
id={`matcher-${index}-operator`}
/>
)}
defaultValue={matcher.operator || matcherFieldOptions[0].value}
name={`matchers.${index}.operator`}
rules={{
required: {
value: required,
message: t('alerting.matchers-field.message.required', 'Required.'),
},
}}
/>
</Field>
<Field
label={t('alerting.matchers-field.label-value', 'Value')}
invalid={!!errors?.matchers?.[index]?.value}
error={errors?.matchers?.[index]?.value?.message}
>
<Input
{...register(`matchers.${index}.value` as const, {
required: {
value: required,
message: t('alerting.matchers-field.message.required', 'Required.'),
},
})}
defaultValue={matcher.value}
placeholder={t('alerting.matchers-field.placeholder-value', 'value')}
id={`matcher-${index}-value`}
/>
</Field>
{(matchers.length > 1 || !required) && (
<IconButton
aria-label={t('alerting.matchers-field.aria-label-remove-matcher', 'Remove matcher')}
className={styles.removeButton}
name="trash-alt"
onClick={() => remove(index)}
>
<Trans i18nKey="alerting.matchers-field.remove">Remove</Trans>
</IconButton>
)}
</div>
);
})}
</div>
<Button
className={styles.indent}
tooltip={t(
'alerting.matchers-field.tooltip-refine-which-alert-instances-silenced-selecting',
'Refine which alert instances are silenced by selecting label matchers'
)}
type="button"
icon="plus"
variant="secondary"
onClick={() => {
const newMatcher = { name: '', value: '', operator: MatcherOperator.equal };
append(newMatcher);
}}
>
<Trans i18nKey="alerting.matchers-field.add-matcher">Add matcher</Trans>
</Button>
</div>
</Field>
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
wrapper: css({
marginTop: theme.spacing(2),
}),
row: css({
marginTop: theme.spacing(1),
display: 'flex',
alignItems: 'flex-start',
flexDirection: 'row',
backgroundColor: theme.colors.background.secondary,
padding: `${theme.spacing(1)} ${theme.spacing(1)} 0 ${theme.spacing(1)}`,
'& > * + *': {
marginLeft: theme.spacing(2),
},
}),
removeButton: css({
marginLeft: theme.spacing(1),
marginTop: theme.spacing(2.5),
}),
matcherOptions: css({
minWidth: '140px',
}),
matchers: css({
maxWidth: `${theme.breakpoints.values.sm}px`,
margin: `${theme.spacing(1)} 0`,
paddingTop: theme.spacing(0.5),
}),
indent: css({
marginLeft: theme.spacing(2),
}),
};
};
export default MatchersField;