MatchersField.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { css, cx } from '@emotion/css';
  2. import React, { FC } from 'react';
  3. import { useFormContext, useFieldArray } from 'react-hook-form';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { Button, Field, Input, IconButton, InputControl, useStyles2, Select } from '@grafana/ui';
  6. import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types';
  7. import { SilenceFormFields } from '../../types/silence-form';
  8. import { matcherFieldOptions } from '../../utils/alertmanager';
  9. interface Props {
  10. className?: string;
  11. }
  12. const MatchersField: FC<Props> = ({ className }) => {
  13. const styles = useStyles2(getStyles);
  14. const formApi = useFormContext<SilenceFormFields>();
  15. const {
  16. control,
  17. register,
  18. formState: { errors },
  19. } = formApi;
  20. const { fields: matchers = [], append, remove } = useFieldArray<SilenceFormFields>({ name: 'matchers' });
  21. return (
  22. <div className={cx(className, styles.wrapper)}>
  23. <Field label="Matching labels" required>
  24. <div>
  25. <div className={styles.matchers}>
  26. {matchers.map((matcher, index) => {
  27. return (
  28. <div className={styles.row} key={`${matcher.id}`} data-testid="matcher">
  29. <Field
  30. label="Label"
  31. invalid={!!errors?.matchers?.[index]?.name}
  32. error={errors?.matchers?.[index]?.name?.message}
  33. >
  34. <Input
  35. {...register(`matchers.${index}.name` as const, {
  36. required: { value: true, message: 'Required.' },
  37. })}
  38. defaultValue={matcher.name}
  39. placeholder="label"
  40. />
  41. </Field>
  42. <Field label={'Operator'}>
  43. <InputControl
  44. control={control}
  45. render={({ field: { onChange, ref, ...field } }) => (
  46. <Select
  47. {...field}
  48. onChange={(value) => onChange(value.value)}
  49. className={styles.matcherOptions}
  50. options={matcherFieldOptions}
  51. aria-label="operator"
  52. />
  53. )}
  54. defaultValue={matcher.operator || matcherFieldOptions[0].value}
  55. name={`matchers.${index}.operator` as const}
  56. rules={{ required: { value: true, message: 'Required.' } }}
  57. />
  58. </Field>
  59. <Field
  60. label="Value"
  61. invalid={!!errors?.matchers?.[index]?.value}
  62. error={errors?.matchers?.[index]?.value?.message}
  63. >
  64. <Input
  65. {...register(`matchers.${index}.value` as const, {
  66. required: { value: true, message: 'Required.' },
  67. })}
  68. defaultValue={matcher.value}
  69. placeholder="value"
  70. />
  71. </Field>
  72. {matchers.length > 1 && (
  73. <IconButton
  74. className={styles.removeButton}
  75. tooltip="Remove matcher"
  76. name={'trash-alt'}
  77. onClick={() => remove(index)}
  78. >
  79. Remove
  80. </IconButton>
  81. )}
  82. </div>
  83. );
  84. })}
  85. </div>
  86. <Button
  87. type="button"
  88. icon="plus"
  89. variant="secondary"
  90. onClick={() => {
  91. const newMatcher = { name: '', value: '', operator: MatcherOperator.equal };
  92. append(newMatcher);
  93. }}
  94. >
  95. Add matcher
  96. </Button>
  97. </div>
  98. </Field>
  99. </div>
  100. );
  101. };
  102. const getStyles = (theme: GrafanaTheme2) => {
  103. return {
  104. wrapper: css`
  105. margin-top: ${theme.spacing(2)};
  106. `,
  107. row: css`
  108. display: flex;
  109. align-items: flex-start;
  110. flex-direction: row;
  111. background-color: ${theme.colors.background.secondary};
  112. padding: ${theme.spacing(1)} ${theme.spacing(1)} 0 ${theme.spacing(1)};
  113. & > * + * {
  114. margin-left: ${theme.spacing(2)};
  115. }
  116. `,
  117. removeButton: css`
  118. margin-left: ${theme.spacing(1)};
  119. margin-top: ${theme.spacing(2.5)};
  120. `,
  121. matcherOptions: css`
  122. min-width: 140px;
  123. `,
  124. matchers: css`
  125. max-width: ${theme.breakpoints.values.sm}px;
  126. margin: ${theme.spacing(1)} 0;
  127. padding-top: ${theme.spacing(0.5)};
  128. `,
  129. };
  130. };
  131. export default MatchersField;