MuteTimingTimeInterval.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { css } from '@emotion/css';
  2. import React from 'react';
  3. import { useFormContext, useFieldArray } from 'react-hook-form';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { Button, Input, Field, FieldSet, useStyles2 } from '@grafana/ui';
  6. import { MuteTimingFields } from '../../types/mute-timing-form';
  7. import { DAYS_OF_THE_WEEK, MONTHS, validateArrayField, defaultTimeInterval } from '../../utils/mute-timings';
  8. import { MuteTimingTimeRange } from './MuteTimingTimeRange';
  9. export const MuteTimingTimeInterval = () => {
  10. const styles = useStyles2(getStyles);
  11. const { formState, register } = useFormContext();
  12. const {
  13. fields: timeIntervals,
  14. append: addTimeInterval,
  15. remove: removeTimeInterval,
  16. } = useFieldArray<MuteTimingFields>({
  17. name: 'time_intervals',
  18. });
  19. return (
  20. <FieldSet className={styles.timeIntervalLegend} label="Time intervals">
  21. <>
  22. <p>
  23. A time interval is a definition for a moment in time. All fields are lists, and at least one list element must
  24. be satisfied to match the field. If a field is left blank, any moment of time will match the field. For an
  25. instant of time to match a complete time interval, all fields must match. A mute timing can contain multiple
  26. time intervals.
  27. </p>
  28. {timeIntervals.map((timeInterval, timeIntervalIndex) => {
  29. const errors = formState.errors;
  30. return (
  31. <div key={timeInterval.id} className={styles.timeIntervalSection}>
  32. <MuteTimingTimeRange intervalIndex={timeIntervalIndex} />
  33. <Field
  34. label="Days of the week"
  35. error={errors.time_intervals?.[timeIntervalIndex]?.weekdays?.message ?? ''}
  36. invalid={!!errors.time_intervals?.[timeIntervalIndex]?.weekdays}
  37. >
  38. <Input
  39. {...register(`time_intervals.${timeIntervalIndex}.weekdays`, {
  40. validate: (value) =>
  41. validateArrayField(
  42. value,
  43. (day) => DAYS_OF_THE_WEEK.includes(day.toLowerCase()),
  44. 'Invalid day of the week'
  45. ),
  46. })}
  47. className={styles.input}
  48. data-testid="mute-timing-weekdays"
  49. // @ts-ignore react-hook-form doesn't handle nested field arrays well
  50. defaultValue={timeInterval.weekdays}
  51. placeholder="Example: monday, tuesday:thursday"
  52. />
  53. </Field>
  54. <Field
  55. label="Days of the month"
  56. description="The days of the month, 1-31, of a month. Negative values can be used to represent days which begin at the end of the month"
  57. invalid={!!errors.time_intervals?.[timeIntervalIndex]?.days_of_month}
  58. error={errors.time_intervals?.[timeIntervalIndex]?.days_of_month?.message}
  59. >
  60. <Input
  61. {...register(`time_intervals.${timeIntervalIndex}.days_of_month`, {
  62. validate: (value) =>
  63. validateArrayField(
  64. value,
  65. (day) => {
  66. const parsedDay = parseInt(day, 10);
  67. return (parsedDay > -31 && parsedDay < 0) || (parsedDay > 0 && parsedDay < 32);
  68. },
  69. 'Invalid day'
  70. ),
  71. })}
  72. className={styles.input}
  73. // @ts-ignore react-hook-form doesn't handle nested field arrays well
  74. defaultValue={timeInterval.days_of_month}
  75. placeholder="Example: 1, 14:16, -1"
  76. data-testid="mute-timing-days"
  77. />
  78. </Field>
  79. <Field
  80. label="Months"
  81. description="The months of the year in either numerical or the full calendar month"
  82. invalid={!!errors.time_intervals?.[timeIntervalIndex]?.months}
  83. error={errors.time_intervals?.[timeIntervalIndex]?.months?.message}
  84. >
  85. <Input
  86. {...register(`time_intervals.${timeIntervalIndex}.months`, {
  87. validate: (value) =>
  88. validateArrayField(
  89. value,
  90. (month) => MONTHS.includes(month) || (parseInt(month, 10) < 13 && parseInt(month, 10) > 0),
  91. 'Invalid month'
  92. ),
  93. })}
  94. className={styles.input}
  95. placeholder="Example: 1:3, may:august, december"
  96. // @ts-ignore react-hook-form doesn't handle nested field arrays well
  97. defaultValue={timeInterval.months}
  98. data-testid="mute-timing-months"
  99. />
  100. </Field>
  101. <Field
  102. label="Years"
  103. invalid={!!errors.time_intervals?.[timeIntervalIndex]?.years}
  104. error={errors.time_intervals?.[timeIntervalIndex]?.years?.message ?? ''}
  105. >
  106. <Input
  107. {...register(`time_intervals.${timeIntervalIndex}.years`, {
  108. validate: (value) => validateArrayField(value, (year) => /^\d{4}$/.test(year), 'Invalid year'),
  109. })}
  110. className={styles.input}
  111. placeholder="Example: 2021:2022, 2030"
  112. // @ts-ignore react-hook-form doesn't handle nested field arrays well
  113. defaultValue={timeInterval.years}
  114. data-testid="mute-timing-years"
  115. />
  116. </Field>
  117. <Button
  118. type="button"
  119. variant="destructive"
  120. icon="trash-alt"
  121. onClick={() => removeTimeInterval(timeIntervalIndex)}
  122. >
  123. Remove time interval
  124. </Button>
  125. </div>
  126. );
  127. })}
  128. <Button
  129. type="button"
  130. variant="secondary"
  131. className={styles.removeTimeIntervalButton}
  132. onClick={() => {
  133. addTimeInterval(defaultTimeInterval);
  134. }}
  135. icon="plus"
  136. >
  137. Add another time interval
  138. </Button>
  139. </>
  140. </FieldSet>
  141. );
  142. };
  143. const getStyles = (theme: GrafanaTheme2) => ({
  144. input: css`
  145. width: 400px;
  146. `,
  147. timeIntervalLegend: css`
  148. legend {
  149. font-size: 1.25rem;
  150. }
  151. `,
  152. timeIntervalSection: css`
  153. background-color: ${theme.colors.background.secondary};
  154. padding: ${theme.spacing(1)};
  155. margin-bottom: ${theme.spacing(1)};
  156. `,
  157. removeTimeIntervalButton: css`
  158. margin-top: ${theme.spacing(1)};
  159. `,
  160. });