diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx index 0bcaa83127468a..fbcd7160efdd98 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx @@ -7,6 +7,7 @@ import React, { Fragment, useEffect, useState } from 'react'; import { IUiSettingsClient, HttpSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; +import { interval } from 'rxjs'; import { AnnotationDomainTypes, Axis, @@ -21,7 +22,14 @@ import { niceTimeFormatter, } from '@elastic/charts'; import moment from 'moment-timezone'; -import { EuiCallOut, EuiLoadingChart, EuiSpacer, EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import { + EuiCallOut, + EuiLoadingChart, + EuiSpacer, + EuiEmptyPrompt, + EuiText, + EuiLoadingSpinner, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { getThresholdAlertVisualizationData } from '../../../../common/lib/index_threshold_api'; import { AggregationType, Comparator } from '../../../../common/types'; @@ -59,7 +67,7 @@ const getTimezone = (uiSettings: IUiSettingsClient) => { return tzOffset; }; -const getDomain = (alertInterval: string) => { +const getDomain = (alertInterval: string, startAt: Date) => { const VISUALIZE_INTERVALS = 30; let intervalMillis: number; @@ -69,10 +77,9 @@ const getDomain = (alertInterval: string) => { intervalMillis = 1000 * 60; // default to one minute if not parseable } - const now = Date.now(); return { - min: now - intervalMillis * VISUALIZE_INTERVALS, - max: now, + min: startAt.getTime() - intervalMillis * VISUALIZE_INTERVALS, + max: startAt.getTime(), }; }; @@ -84,6 +91,15 @@ interface Props { [key: string]: Comparator; }; alertsContext: AlertsContextValue; + refreshRateInMilliseconds: number; +} + +const DEFAULT_REFRESH_RATE = 5000; + +enum LoadingStateType { + FirstLoad, + Refresh, + Idle, } type MetricResult = [number, number]; // [epochMillis, value] @@ -93,6 +109,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ aggregationTypes, comparators, alertsContext, + refreshRateInMilliseconds = DEFAULT_REFRESH_RATE, }) => { const { index, @@ -109,14 +126,25 @@ export const ThresholdVisualization: React.FunctionComponent = ({ } = alertParams; const { http, toastNotifications, charts, uiSettings, dataFieldsFormats } = alertsContext; - const [isLoading, setIsLoading] = useState(false); + const [loadingState, setLoadingState] = useState(null); const [error, setError] = useState(undefined); const [visualizationData, setVisualizationData] = useState>(); + const [startVisualizationAt, setStartVisualizationAt] = useState(new Date()); + + useEffect(() => { + const source = interval(refreshRateInMilliseconds); + const subscription = source.subscribe((val: number) => { + setStartVisualizationAt(new Date()); + }); + return () => { + subscription.unsubscribe(); + }; + }, [refreshRateInMilliseconds]); useEffect(() => { (async () => { try { - setIsLoading(true); + setLoadingState(loadingState ? LoadingStateType.Refresh : LoadingStateType.FirstLoad); setVisualizationData( await getVisualizationData(alertWithoutActions, visualizeOptions, http) ); @@ -131,7 +159,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ } setError(e); } finally { - setIsLoading(false); + setLoadingState(LoadingStateType.Idle); } })(); /* eslint-disable react-hooks/exhaustive-deps */ @@ -147,6 +175,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ timeWindowUnit, groupBy, threshold, + startVisualizationAt, ]); /* eslint-enable react-hooks/exhaustive-deps */ @@ -155,7 +184,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ } const chartsTheme = charts.theme.useChartsTheme(); - const domain = getDomain(alertInterval); + const domain = getDomain(alertInterval, startVisualizationAt); const visualizeOptions = { rangeFrom: new Date(domain.min).toISOString(), rangeTo: new Date(domain.max).toISOString(), @@ -165,7 +194,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ // Fetching visualization data is independent of alert actions const alertWithoutActions = { ...alertParams, actions: [], type: 'threshold' }; - if (isLoading) { + if (loadingState === LoadingStateType.FirstLoad) { return ( } @@ -224,7 +253,12 @@ export const ThresholdVisualization: React.FunctionComponent = ({ const dateFormatter = niceTimeFormatter([domain.min, domain.max]); const aggLabel = aggregationTypes[aggType].text; return ( -
+
+ {loadingState === LoadingStateType.Refresh ? ( + + ) : ( + + )} {alertVisualizationDataKeys.length ? (