mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add DateTimePicker on view rule page (#38592)
* export datetimepicker * minor fixes to the datetime picker * correct datetime to picker * move datetime calc to function * set maxDate * set maxDate * wrap in useCallback
This commit is contained in:
@@ -3,7 +3,7 @@ import { useMedia } from 'react-use';
|
||||
import Calendar from 'react-calendar/dist/entry.nostyle';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@grafana/data';
|
||||
import { Button, ClickOutsideWrapper, Field, HorizontalGroup, Icon, Input, Portal } from '../..';
|
||||
import { Button, ClickOutsideWrapper, HorizontalGroup, Icon, InlineField, Input, Portal } from '../..';
|
||||
import { TimeOfDayPicker } from '../TimeOfDayPicker';
|
||||
import { getBodyStyles, getStyles as getCalendarStyles } from '../TimeRangePicker/TimePickerCalendar';
|
||||
import { useStyles2, useTheme2 } from '../../../themes';
|
||||
@@ -16,11 +16,13 @@ export interface Props {
|
||||
onChange: (date: DateTime) => void;
|
||||
/** label for the input field */
|
||||
label?: ReactNode;
|
||||
/** Set the latest selectable date */
|
||||
maxDate?: Date;
|
||||
}
|
||||
|
||||
const stopPropagation = (event: React.MouseEvent<HTMLDivElement>) => event.stopPropagation();
|
||||
|
||||
export const DateTimePicker: FC<Props> = ({ date, label, onChange }) => {
|
||||
export const DateTimePicker: FC<Props> = ({ date, maxDate, label, onChange }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
const theme = useTheme2();
|
||||
@@ -50,7 +52,13 @@ export const DateTimePicker: FC<Props> = ({ date, label, onChange }) => {
|
||||
{isOpen ? (
|
||||
isFullscreen ? (
|
||||
<ClickOutsideWrapper onClick={() => setOpen(false)}>
|
||||
<DateTimeCalendar date={date} onChange={onApply} isFullscreen={true} onClose={() => setOpen(false)} />
|
||||
<DateTimeCalendar
|
||||
date={date}
|
||||
onChange={onApply}
|
||||
isFullscreen={true}
|
||||
onClose={() => setOpen(false)}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
</ClickOutsideWrapper>
|
||||
) : (
|
||||
<Portal>
|
||||
@@ -72,6 +80,7 @@ interface DateTimeCalendarProps {
|
||||
onChange: (date: DateTime) => void;
|
||||
onClose: () => void;
|
||||
isFullscreen: boolean;
|
||||
maxDate?: Date;
|
||||
}
|
||||
|
||||
interface InputProps {
|
||||
@@ -127,11 +136,13 @@ const DateTimeInput: FC<InputProps> = ({ date, label, onChange, isFullscreen, on
|
||||
|
||||
const icon = <Button icon="calendar-alt" variant="secondary" onClick={onOpen} />;
|
||||
return (
|
||||
<Field
|
||||
<InlineField
|
||||
label={label}
|
||||
onClick={stopPropagation}
|
||||
invalid={!!(internalDate.value && internalDate.invalid)}
|
||||
error="Incorrect date format"
|
||||
className={css`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Input
|
||||
onClick={stopPropagation}
|
||||
@@ -143,11 +154,11 @@ const DateTimeInput: FC<InputProps> = ({ date, label, onChange, isFullscreen, on
|
||||
data-testid="date-time-input"
|
||||
placeholder="Select date/time"
|
||||
/>
|
||||
</Field>
|
||||
</InlineField>
|
||||
);
|
||||
};
|
||||
|
||||
const DateTimeCalendar: FC<DateTimeCalendarProps> = ({ date, onClose, onChange, isFullscreen }) => {
|
||||
const DateTimeCalendar: FC<DateTimeCalendarProps> = ({ date, onClose, onChange, isFullscreen, maxDate }) => {
|
||||
const calendarStyles = useStyles2(getBodyStyles);
|
||||
const styles = useStyles2(getStyles);
|
||||
const [internalDate, setInternalDate] = useState<Date>(() => {
|
||||
@@ -188,6 +199,7 @@ const DateTimeCalendar: FC<DateTimeCalendarProps> = ({ date, onClose, onChange,
|
||||
locale="en"
|
||||
className={calendarStyles.body}
|
||||
tileClassName={calendarStyles.title}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
<div className={styles.time}>
|
||||
<TimeOfDayPicker showSeconds={true} onChange={onChangeTime} value={dateTime(internalDate)} />
|
||||
@@ -210,6 +222,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
border: 1px ${theme.colors.border.weak} solid;
|
||||
border-radius: ${theme.shape.borderRadius(1)};
|
||||
background-color: ${theme.colors.background.primary};
|
||||
z-index: ${theme.zIndex.modal};
|
||||
`,
|
||||
fullScreen: css`
|
||||
position: absolute;
|
||||
|
||||
@@ -29,6 +29,7 @@ export {
|
||||
DatePickerWithInput,
|
||||
DatePickerWithInputProps,
|
||||
} from './DateTimePickers/DatePickerWithInput/DatePickerWithInput';
|
||||
export { DateTimePicker } from './DateTimePickers/DateTimePicker/DateTimePicker';
|
||||
export { List } from './List/List';
|
||||
export { TagsInput } from './TagsInput/TagsInput';
|
||||
export { Pagination } from './Pagination/Pagination';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2, LoadingState, PanelData } from '@grafana/data';
|
||||
@@ -27,6 +27,7 @@ import { AlertLabels } from './components/AlertLabels';
|
||||
import { RuleDetailsExpression } from './components/rules/RuleDetailsExpression';
|
||||
import { RuleDetailsAnnotations } from './components/rules/RuleDetailsAnnotations';
|
||||
import * as ruleId from './utils/rule-id';
|
||||
import { AlertQuery } from '../../../types/unified-alerting-dto';
|
||||
|
||||
type RuleViewerProps = GrafanaRouteComponentProps<{ id?: string; sourceName?: string }>;
|
||||
|
||||
@@ -41,7 +42,8 @@ export function RuleViewer({ match }: RuleViewerProps) {
|
||||
const { loading, error, result: rule } = useCombinedRule(identifier, sourceName);
|
||||
const runner = useMemo(() => new AlertingQueryRunner(), []);
|
||||
const data = useObservable(runner.get());
|
||||
const queries = useMemo(() => alertRuleToQueries(rule), [rule]);
|
||||
const queries2 = useMemo(() => alertRuleToQueries(rule), [rule]);
|
||||
const [queries, setQueries] = useState<AlertQuery[]>([]);
|
||||
|
||||
const onRunQueries = useCallback(() => {
|
||||
if (queries.length > 0) {
|
||||
@@ -49,6 +51,10 @@ export function RuleViewer({ match }: RuleViewerProps) {
|
||||
}
|
||||
}, [queries, runner]);
|
||||
|
||||
useEffect(() => {
|
||||
setQueries(queries2);
|
||||
}, [queries2]);
|
||||
|
||||
useEffect(() => {
|
||||
onRunQueries();
|
||||
}, [onRunQueries]);
|
||||
@@ -57,6 +63,17 @@ export function RuleViewer({ match }: RuleViewerProps) {
|
||||
return () => runner.destroy();
|
||||
}, [runner]);
|
||||
|
||||
const onChangeQuery = useCallback((query: AlertQuery) => {
|
||||
setQueries((queries) =>
|
||||
queries.map((q) => {
|
||||
if (q.refId === query.refId) {
|
||||
return query;
|
||||
}
|
||||
return q;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (!sourceName) {
|
||||
return (
|
||||
<RuleViewerLayout title={pageTitle}>
|
||||
@@ -143,7 +160,11 @@ export function RuleViewer({ match }: RuleViewerProps) {
|
||||
{queries.map((query) => {
|
||||
return (
|
||||
<div key={query.refId} className={styles.query}>
|
||||
<RuleViewerVisualization query={query} data={data && data[query.refId]} />
|
||||
<RuleViewerVisualization
|
||||
query={query}
|
||||
data={data && data[query.refId]}
|
||||
onChangeQuery={onChangeQuery}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme2, PanelData, urlUtil } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, DateTime, dateTime, GrafanaTheme2, PanelData, urlUtil } from '@grafana/data';
|
||||
import { config, getDataSourceSrv, PanelRenderer } from '@grafana/runtime';
|
||||
import { Alert, CodeEditor, LinkButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { Alert, CodeEditor, DateTimePicker, LinkButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { isExpressionQuery } from 'app/features/expressions/guards';
|
||||
import { PanelOptions } from 'app/plugins/panel/table/models.gen';
|
||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
@@ -13,6 +13,7 @@ import { TABLE, TIMESERIES } from '../../utils/constants';
|
||||
type RuleViewerVisualizationProps = {
|
||||
data?: PanelData;
|
||||
query: AlertQuery;
|
||||
onChangeQuery: (query: AlertQuery) => void;
|
||||
};
|
||||
|
||||
const headerHeight = 4;
|
||||
@@ -20,15 +21,35 @@ const headerHeight = 4;
|
||||
export function RuleViewerVisualization(props: RuleViewerVisualizationProps): JSX.Element | null {
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getStyles);
|
||||
const { data, query } = props;
|
||||
const { data, query, onChangeQuery } = props;
|
||||
const defaultPanel = isExpressionQuery(query.model) ? TABLE : TIMESERIES;
|
||||
const [panel, setPanel] = useState<SupportedPanelPlugins>(defaultPanel);
|
||||
const dsSettings = getDataSourceSrv().getInstanceSettings(query.datasourceUid);
|
||||
const relativeTimeRange = query.relativeTimeRange;
|
||||
const [options, setOptions] = useState<PanelOptions>({
|
||||
frameIndex: 0,
|
||||
showHeader: true,
|
||||
});
|
||||
|
||||
const onTimeChange = useCallback(
|
||||
(newDateTime: DateTime) => {
|
||||
const now = dateTime().unix() - newDateTime.unix();
|
||||
|
||||
if (relativeTimeRange) {
|
||||
const interval = relativeTimeRange.from - relativeTimeRange.to;
|
||||
onChangeQuery({
|
||||
...query,
|
||||
relativeTimeRange: { from: now + interval, to: now },
|
||||
});
|
||||
}
|
||||
},
|
||||
[onChangeQuery, query, relativeTimeRange]
|
||||
);
|
||||
|
||||
const setDateTime = useCallback((relativeTimeRangeTo: number) => {
|
||||
return relativeTimeRangeTo === 0 ? dateTime() : dateTime().subtract(relativeTimeRangeTo, 'seconds');
|
||||
}, []);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
@@ -62,12 +83,19 @@ export function RuleViewerVisualization(props: RuleViewerVisualizationProps): JS
|
||||
<span className={styles.dataSource}>({dsSettings.name})</span>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<PanelPluginsButtonGroup onChange={setPanel} value={panel} size="sm" />
|
||||
{!isExpressionQuery(query.model) && relativeTimeRange ? (
|
||||
<DateTimePicker
|
||||
date={setDateTime(relativeTimeRange.to)}
|
||||
onChange={onTimeChange}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
) : null}
|
||||
<PanelPluginsButtonGroup onChange={setPanel} value={panel} size="md" />
|
||||
{!isExpressionQuery(query.model) && (
|
||||
<>
|
||||
<div className={styles.spacing} />
|
||||
<LinkButton
|
||||
size="sm"
|
||||
size="md"
|
||||
variant="secondary"
|
||||
icon="compass"
|
||||
target="_blank"
|
||||
|
||||
Reference in New Issue
Block a user