mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Access query details of provisioned alerts (#59626)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user