Alerting: Access query details of provisioned alerts (#59626)

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
Konrad Lalik
2022-12-15 13:33:41 +01:00
committed by GitHub
parent b7fc837c35
commit 384322dc14
9 changed files with 767 additions and 234 deletions

View File

@@ -1,6 +1,7 @@
import { css, cx } from '@emotion/css';
import React, { FC, FormEvent, ReactNode, useCallback, useEffect, useState } from 'react';
import Calendar from 'react-calendar';
import { usePopper } from 'react-popper';
import { useMedia } from 'react-use';
import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@grafana/data';
@@ -33,6 +34,13 @@ export const DateTimePicker: FC<Props> = ({ date, maxDate, label, onChange }) =>
const isFullscreen = useMedia(`(min-width: ${theme.breakpoints.values.lg}px)`);
const styles = useStyles2(getStyles);
const [markerElement, setMarkerElement] = useState<HTMLInputElement | null>();
const [selectorElement, setSelectorElement] = useState<HTMLDivElement | null>();
const popper = usePopper(markerElement, selectorElement, {
placement: 'bottom-start',
});
const onApply = useCallback(
(date: DateTime) => {
setOpen(false);
@@ -51,18 +59,29 @@ export const DateTimePicker: FC<Props> = ({ date, maxDate, label, onChange }) =>
return (
<div data-testid="date-time-picker" style={{ position: 'relative' }}>
<DateTimeInput date={date} onChange={onChange} isFullscreen={isFullscreen} onOpen={onOpen} label={label} />
<DateTimeInput
date={date}
onChange={onChange}
isFullscreen={isFullscreen}
onOpen={onOpen}
label={label}
ref={setMarkerElement}
/>
{isOpen ? (
isFullscreen ? (
<ClickOutsideWrapper onClick={() => setOpen(false)}>
<DateTimeCalendar
date={date}
onChange={onApply}
isFullscreen={true}
onClose={() => setOpen(false)}
maxDate={maxDate}
/>
</ClickOutsideWrapper>
<Portal>
<ClickOutsideWrapper onClick={() => setOpen(false)}>
<DateTimeCalendar
date={date}
onChange={onApply}
isFullscreen={true}
onClose={() => setOpen(false)}
maxDate={maxDate}
ref={setSelectorElement}
style={popper.styles.popper}
/>
</ClickOutsideWrapper>
</Portal>
) : (
<Portal>
<ClickOutsideWrapper onClick={() => setOpen(false)}>
@@ -84,6 +103,7 @@ interface DateTimeCalendarProps {
onClose: () => void;
isFullscreen: boolean;
maxDate?: Date;
style?: React.CSSProperties;
}
interface InputProps {
@@ -99,127 +119,141 @@ type InputState = {
invalid: boolean;
};
const DateTimeInput: FC<InputProps> = ({ date, label, onChange, isFullscreen, onOpen }) => {
const [internalDate, setInternalDate] = useState<InputState>(() => {
return { value: date ? dateTimeFormat(date) : dateTimeFormat(dateTime()), invalid: false };
});
useEffect(() => {
if (date) {
setInternalDate({
invalid: !isValid(dateTimeFormat(date)),
value: isDateTime(date) ? dateTimeFormat(date) : date,
});
}
}, [date]);
const onChangeDate = useCallback((event: FormEvent<HTMLInputElement>) => {
const isInvalid = !isValid(event.currentTarget.value);
setInternalDate({
value: event.currentTarget.value,
invalid: isInvalid,
const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
({ date, label, onChange, isFullscreen, onOpen }, ref) => {
const [internalDate, setInternalDate] = useState<InputState>(() => {
return { value: date ? dateTimeFormat(date) : dateTimeFormat(dateTime()), invalid: false };
});
}, []);
const onFocus = useCallback(
(event: FormEvent<HTMLElement>) => {
if (!isFullscreen) {
return;
useEffect(() => {
if (date) {
setInternalDate({
invalid: !isValid(dateTimeFormat(date)),
value: isDateTime(date) ? dateTimeFormat(date) : date,
});
}
onOpen(event);
},
[isFullscreen, onOpen]
);
}, [date]);
const onBlur = useCallback(() => {
if (isDateTime(internalDate.value)) {
onChange(dateTime(internalDate.value));
}
}, [internalDate.value, onChange]);
const icon = <Button aria-label="Time picker" icon="calendar-alt" variant="secondary" onClick={onOpen} />;
return (
<InlineField
label={label}
onClick={stopPropagation}
invalid={!!(internalDate.value && internalDate.invalid)}
className={css`
margin-bottom: 0;
`}
>
<Input
onClick={stopPropagation}
onChange={onChangeDate}
addonAfter={icon}
value={internalDate.value}
onFocus={onFocus}
onBlur={onBlur}
data-testid="date-time-input"
placeholder="Select date/time"
/>
</InlineField>
);
};
const DateTimeCalendar: FC<DateTimeCalendarProps> = ({ date, onClose, onChange, isFullscreen, maxDate }) => {
const calendarStyles = useStyles2(getBodyStyles);
const styles = useStyles2(getStyles);
const [internalDate, setInternalDate] = useState<Date>(() => {
if (date && date.isValid()) {
return date.toDate();
}
return new Date();
});
const onChangeDate = useCallback((date: Date | Date[]) => {
if (!Array.isArray(date)) {
setInternalDate((prevState) => {
// If we don't use time from prevState
// the time will be reset to 00:00:00
date.setHours(prevState.getHours());
date.setMinutes(prevState.getMinutes());
date.setSeconds(prevState.getSeconds());
return date;
const onChangeDate = useCallback((event: FormEvent<HTMLInputElement>) => {
const isInvalid = !isValid(event.currentTarget.value);
setInternalDate({
value: event.currentTarget.value,
invalid: isInvalid,
});
}
}, []);
}, []);
const onChangeTime = useCallback((date: DateTime) => {
setInternalDate(date.toDate());
}, []);
const onFocus = useCallback(
(event: FormEvent<HTMLElement>) => {
if (!isFullscreen) {
return;
}
onOpen(event);
},
[isFullscreen, onOpen]
);
return (
<div className={cx(styles.container, { [styles.fullScreen]: isFullscreen })} onClick={stopPropagation}>
<Calendar
next2Label={null}
prev2Label={null}
value={internalDate}
nextLabel={<Icon name="angle-right" />}
nextAriaLabel="Next month"
prevLabel={<Icon name="angle-left" />}
prevAriaLabel="Previous month"
onChange={onChangeDate}
locale="en"
className={calendarStyles.body}
tileClassName={calendarStyles.title}
maxDate={maxDate}
/>
<div className={styles.time}>
<TimeOfDayPicker showSeconds={true} onChange={onChangeTime} value={dateTime(internalDate)} />
const onBlur = useCallback(() => {
if (isDateTime(internalDate.value)) {
onChange(dateTime(internalDate.value));
}
}, [internalDate.value, onChange]);
const icon = <Button aria-label="Time picker" icon="calendar-alt" variant="secondary" onClick={onOpen} />;
return (
<InlineField
label={label}
onClick={stopPropagation}
invalid={!!(internalDate.value && internalDate.invalid)}
className={css`
margin-bottom: 0;
`}
>
<Input
onClick={stopPropagation}
onChange={onChangeDate}
addonAfter={icon}
value={internalDate.value}
onFocus={onFocus}
onBlur={onBlur}
data-testid="date-time-input"
placeholder="Select date/time"
ref={ref}
/>
</InlineField>
);
}
);
DateTimeInput.displayName = 'DateTimeInput';
const DateTimeCalendar = React.forwardRef<HTMLDivElement, DateTimeCalendarProps>(
({ date, onClose, onChange, isFullscreen, maxDate, style }, ref) => {
const calendarStyles = useStyles2(getBodyStyles);
const styles = useStyles2(getStyles);
const [internalDate, setInternalDate] = useState<Date>(() => {
if (date && date.isValid()) {
return date.toDate();
}
return new Date();
});
const onChangeDate = useCallback((date: Date | Date[]) => {
if (!Array.isArray(date)) {
setInternalDate((prevState) => {
// If we don't use time from prevState
// the time will be reset to 00:00:00
date.setHours(prevState.getHours());
date.setMinutes(prevState.getMinutes());
date.setSeconds(prevState.getSeconds());
return date;
});
}
}, []);
const onChangeTime = useCallback((date: DateTime) => {
setInternalDate(date.toDate());
}, []);
return (
<div
className={cx(styles.container, { [styles.fullScreen]: isFullscreen })}
style={style}
onClick={stopPropagation}
ref={ref}
>
<Calendar
next2Label={null}
prev2Label={null}
value={internalDate}
nextLabel={<Icon name="angle-right" />}
nextAriaLabel="Next month"
prevLabel={<Icon name="angle-left" />}
prevAriaLabel="Previous month"
onChange={onChangeDate}
locale="en"
className={calendarStyles.body}
tileClassName={calendarStyles.title}
maxDate={maxDate}
/>
<div className={styles.time}>
<TimeOfDayPicker showSeconds={true} onChange={onChangeTime} value={dateTime(internalDate)} />
</div>
<HorizontalGroup>
<Button type="button" onClick={() => onChange(dateTime(internalDate))}>
Apply
</Button>
<Button variant="secondary" type="button" onClick={onClose}>
Cancel
</Button>
</HorizontalGroup>
</div>
<HorizontalGroup>
<Button type="button" onClick={() => onChange(dateTime(internalDate))}>
Apply
</Button>
<Button variant="secondary" type="button" onClick={onClose}>
Cancel
</Button>
</HorizontalGroup>
</div>
);
};
);
}
);
DateTimeCalendar.displayName = 'DateTimeCalendar';
const getStyles = (theme: GrafanaTheme2) => ({
container: css`