diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index e2a6915fd79b6..fdbee30ce12cc 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -5,15 +5,25 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiFlyoutSize } from '@elastic/eui'; - import React, { useEffect, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { TimeBuckets, UI_SETTINGS } from '@kbn/data-plugin/common'; import { BoolQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { loadRuleAggregations } from '@kbn/triggers-actions-ui-plugin/public'; +import { + loadRuleAggregations, + AlertSummaryTimeRange, +} from '@kbn/triggers-actions-ui-plugin/public'; import { AlertConsumers } from '@kbn/rule-data-utils'; -import { ObservabilityAlertSearchbarWithUrlSync } from '../../../../components/shared/alert_search_bar'; +import { useToasts } from '../../../../hooks/use_toast'; +import { + alertSearchBarStateContainer, + Provider, + useAlertSearchBarStateContainer, +} from '../../../../components/shared/alert_search_bar/containers'; +import { getAlertSummaryTimeRange } from '../../../rule_details/helpers'; +import { ObservabilityAlertSearchBar } from '../../../../components/shared/alert_search_bar'; import { observabilityAlertFeatureIds } from '../../../../config'; import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; import { observabilityFeatureId } from '../../../../../common'; @@ -33,15 +43,27 @@ import { } from './constants'; import { RuleStatsState } from './types'; -export function AlertsPage() { +function InternalAlertsPage() { const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext(); const { cases, + data: { + query: { + timefilter: { timefilter: timeFilterService }, + }, + }, docLinks, http, notifications: { toasts }, - triggersActionsUi: { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable }, + triggersActionsUi: { + alertsTableConfigurationRegistry, + getAlertsSearchBar: AlertsSearchBar, + getAlertsStateTable: AlertsStateTable, + getAlertSummaryWidget: AlertSummaryWidget, + }, + uiSettings, } = useKibana().services; + const alertSearchBarStateProps = useAlertSearchBarStateContainer(URL_STORAGE_KEY); const [ruleStatsLoading, setRuleStatsLoading] = useState(false); const [ruleStats, setRuleStats] = useState({ @@ -53,6 +75,19 @@ export function AlertsPage() { }); const { hasAnyData, isAllRequestsComplete } = useHasData(); const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>(); + const timeBuckets = new TimeBuckets({ + 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), + dateFormat: uiSettings.get('dateFormat'), + 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), + }); + const alertSummaryTimeRange: AlertSummaryTimeRange = getAlertSummaryTimeRange( + { + from: alertSearchBarStateProps.rangeFrom, + to: alertSearchBarStateProps.rangeTo, + }, + timeBuckets + ); useBreadcrumbs([ { @@ -132,15 +167,23 @@ export function AlertsPage() { rightSideItems: renderRuleStats(ruleStats, manageRulesHref, ruleStatsLoading), }} > - + - + + + - ); } + +export function AlertsPage() { + return ( + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.test.tsx b/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.test.tsx index 9c437f5f5da64..d63cce34755c1 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.test.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.test.tsx @@ -6,14 +6,63 @@ */ import moment from 'moment'; -import { getAlertSummaryWidgetTimeRange } from '.'; +import { TimeBuckets } from '@kbn/data-plugin/common'; +import { getAlertSummaryTimeRange, getDefaultAlertSummaryTimeRange } from '.'; -describe('getDefaultAlertSummaryTimeRange', () => { - it('should return default time in UTC format', () => { - const defaultTimeRange = getAlertSummaryWidgetTimeRange(); - const utcFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; +describe('AlertSummaryTimeRange', () => { + describe('getDefaultAlertSummaryTimeRange', () => { + it('should return default time in UTC format', () => { + const defaultTimeRange = getDefaultAlertSummaryTimeRange(); + const utcFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; - expect(moment(defaultTimeRange.utcFrom, utcFormat, true).isValid()).toBeTruthy(); - expect(moment(defaultTimeRange.utcTo, utcFormat, true).isValid()).toBeTruthy(); + expect(moment(defaultTimeRange.utcFrom, utcFormat, true).isValid()).toBeTruthy(); + expect(moment(defaultTimeRange.utcTo, utcFormat, true).isValid()).toBeTruthy(); + }); + }); + + describe('getAlertSummaryTimeRange', () => { + const timeBucketConfig = { + 'histogram:maxBars': 4, + 'histogram:barTarget': 3, + dateFormat: 'YYYY-MM-DD', + 'dateFormat:scaled': [ + ['', 'HH:mm:ss.SSS'], + ['PT1S', 'HH:mm:ss'], + ['PT1M', 'HH:mm'], + ['PT1H', 'YYYY-MM-DD HH:mm'], + ['P1DT', 'YYYY-MM-DD'], + ['P1YT', 'YYYY'], + ], + }; + const timeBuckets = new TimeBuckets(timeBucketConfig); + + it.each([ + // 15 minutes + ['2023-01-09T12:07:54.441Z', '2023-01-09T12:22:54.441Z', '30s', 'HH:mm:ss'], + // 30 minutes + ['2023-01-09T11:53:43.605Z', '2023-01-09T12:23:43.605Z', '30s', 'HH:mm:ss'], + // 1 hour + ['2023-01-09T11:22:05.728Z', '2023-01-09T12:22:05.728Z', '60s', 'HH:mm'], + // 24 hours + ['2023-01-08T12:00:00.000Z', '2023-01-09T12:24:30.853Z', '1800s', 'HH:mm'], + // 7 days + ['2023-01-01T23:00:00.000Z', '2023-01-09T12:29:38.101Z', '10800s', 'YYYY-MM-DD HH:mm'], + // 30 days + ['2022-12-09T23:00:00.000Z', '2023-01-09T12:30:13.717Z', '43200s', 'YYYY-MM-DD HH:mm'], + // 90 days + ['2022-10-10T22:00:00.000Z', '2023-01-09T12:32:11.537Z', '86400s', 'YYYY-MM-DD'], + // 1 year + ['2022-01-08T23:00:00.000Z', '2023-01-09T12:33:09.906Z', '86400s', 'YYYY-MM-DD'], + ])( + `Input: [%s, %s], Output: interval: %s, time format: %s `, + (from, to, fixedInterval, dateFormat) => { + expect(getAlertSummaryTimeRange({ from, to }, timeBuckets)).toEqual({ + utcFrom: from, + utcTo: to, + fixedInterval, + dateFormat, + }); + } + ); }); }); diff --git a/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.tsx b/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.tsx index 973bfc628ea60..decfa03fb6f8c 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/helpers/get_alert_summary_time_range.tsx @@ -5,12 +5,15 @@ * 2.0. */ -import type { AlertSummaryTimeRange } from '@kbn/triggers-actions-ui-plugin/public/application/hooks/use_load_alert_summary'; import React from 'react'; -import { getAbsoluteTimeRange } from '@kbn/data-plugin/common'; +import { getAbsoluteTimeRange, TimeBuckets } from '@kbn/data-plugin/common'; +import { TimeRange } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { AlertSummaryTimeRange } from '@kbn/triggers-actions-ui-plugin/public'; +import { getAbsoluteTime } from '../../../utils/date'; +import { getBucketSize } from '../../../utils/get_bucket_size'; -export const getAlertSummaryWidgetTimeRange = (): AlertSummaryTimeRange => { +export const getDefaultAlertSummaryTimeRange = (): AlertSummaryTimeRange => { const { to, from } = getAbsoluteTimeRange({ from: 'now-30d', to: 'now', @@ -28,3 +31,30 @@ export const getAlertSummaryWidgetTimeRange = (): AlertSummaryTimeRange => { ), }; }; + +export const getAlertSummaryTimeRange = ( + timeRange: TimeRange, + timeBuckets: TimeBuckets +): AlertSummaryTimeRange => { + const { to, from } = getAbsoluteTimeRange(timeRange); + const fixedInterval = getFixedInterval(timeRange); + timeBuckets.setInterval(fixedInterval); + + return { + utcFrom: from, + utcTo: to, + fixedInterval, + dateFormat: timeBuckets.getScaledDateFormat(), + }; +}; + +const getFixedInterval = ({ to, from }: TimeRange) => { + const start = getAbsoluteTime(from); + const end = getAbsoluteTime(to, { roundUp: true }); + + if (start && end) { + return getBucketSize({ start, end, minInterval: '30s', buckets: 60 }).intervalString; + } + + return '1m'; +}; diff --git a/x-pack/plugins/observability/public/pages/rule_details/helpers/index.ts b/x-pack/plugins/observability/public/pages/rule_details/helpers/index.ts index e93f2f4cc5ee0..5ba979bf5a0fe 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/helpers/index.ts +++ b/x-pack/plugins/observability/public/pages/rule_details/helpers/index.ts @@ -5,4 +5,7 @@ * 2.0. */ -export { getAlertSummaryWidgetTimeRange } from './get_alert_summary_time_range'; +export { + getDefaultAlertSummaryTimeRange, + getAlertSummaryTimeRange, +} from './get_alert_summary_time_range'; diff --git a/x-pack/plugins/observability/public/pages/rule_details/index.tsx b/x-pack/plugins/observability/public/pages/rule_details/index.tsx index 420757b7b50bd..fa8c2273b0ac0 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/index.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/index.tsx @@ -42,7 +42,7 @@ import { fromQuery, toQuery } from '../../utils/url'; import { ObservabilityAlertSearchbarWithUrlSync } from '../../components/shared/alert_search_bar'; import { DeleteModalConfirmation } from './components/delete_modal_confirmation'; import { CenterJustifiedSpinner } from './components/center_justified_spinner'; -import { getAlertSummaryWidgetTimeRange } from './helpers'; +import { getDefaultAlertSummaryTimeRange } from './helpers'; import { EXECUTION_TAB, @@ -112,7 +112,7 @@ export function RuleDetailsPage() { const [isRuleEditPopoverOpen, setIsRuleEditPopoverOpen] = useState(false); const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>(); const [alertSummaryWidgetTimeRange, setAlertSummaryWidgetTimeRange] = useState( - getAlertSummaryWidgetTimeRange + getDefaultAlertSummaryTimeRange ); const ruleQuery = useRef([ { query: `kibana.alert.rule.uuid: ${ruleId}`, language: 'kuery' }, @@ -125,7 +125,7 @@ export function RuleDetailsPage() { const tabsRef = useRef(null); const onAlertSummaryWidgetClick = async (status: AlertStatus = ALERT_STATUS_ALL) => { - const timeRange = getAlertSummaryWidgetTimeRange(); + const timeRange = getDefaultAlertSummaryTimeRange(); setAlertSummaryWidgetTimeRange(timeRange); await locators.get(ruleDetailsLocatorID)?.navigate( { diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts index 495fc766cd62f..ca1afaf41c1a6 100644 --- a/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts @@ -14,13 +14,15 @@ export function getBucketSize({ start, end, minInterval, + buckets = 100, }: { start: number; end: number; minInterval: string; + buckets?: number; }) { const duration = moment.duration(end - start, 'ms'); - const bucketSize = Math.max(calculateAuto.near(100, duration)?.asSeconds() ?? 0, 1); + const bucketSize = Math.max(calculateAuto.near(buckets, duration)?.asSeconds() ?? 0, 1); const intervalString = `${bucketSize}s`; const matches = minInterval && minInterval.match(/^([\d]+)([shmdwMy]|ms)$/); const minBucketSize = matches ? Number(matches[1]) * unitToSeconds(matches[2]) : 0; diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_summary.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_summary.test.ts index 3496b8f342e2c..e15f2d40dc826 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_summary.test.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_summary.test.ts @@ -88,7 +88,7 @@ describe('getAlertSummaryRoute', () => { "attributes": Object { "success": false, }, - "message": "fixed_interval is not following the expected format 1m, 1h, 1d, 1w", + "message": "fixed_interval (value: xx) is not following the expected format 1s, 1m, 1h, 1d with at most 6 digits", } `); }); diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_summary.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_summary.ts index 6125c2a39b845..838bf06e7e768 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_summary.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_summary.ts @@ -57,9 +57,9 @@ export const getAlertSummaryRoute = (router: IRouter) throw Boom.badRequest('gte and/or lte are not following the UTC format'); } - if (fixedInterval && fixedInterval?.match(/^\d{1,2}['m','h','d','w']$/) == null) { + if (fixedInterval && fixedInterval?.match(/^\d{1,6}['s','m','h','d']$/) == null) { throw Boom.badRequest( - 'fixed_interval is not following the expected format 1m, 1h, 1d, 1w' + `fixed_interval (value: ${fixedInterval}) is not following the expected format 1s, 1m, 1h, 1d with at most 6 digits` ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.ts index 25c670d787a09..e3dbeb2a3b525 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.ts @@ -12,19 +12,10 @@ import { AsApiContract } from '@kbn/actions-plugin/common'; import { HttpSetup } from '@kbn/core/public'; import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants'; import { useKibana } from '../../common/lib/kibana'; - -export interface AlertSummaryTimeRange { - utcFrom: string; - utcTo: string; - // fixed_interval condition in ES query such as '1m', '1d' - fixedInterval: string; - title: JSX.Element | string; -} - -export interface Alert { - key: number; - doc_count: number; -} +import { + Alert, + AlertSummaryTimeRange, +} from '../sections/rule_details/components/alert_summary/types'; interface UseLoadAlertSummaryProps { featureIds?: ValidFeatureId[]; @@ -75,8 +66,7 @@ export function useLoadAlertSummary({ featureIds, timeRange, filter }: UseLoadAl }); if (!isCancelledRef.current) { - setAlertSummary((oldState) => ({ - ...oldState, + setAlertSummary(() => ({ alertSummary: { activeAlertCount, activeAlerts, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/mock/alert_summary_widget/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/mock/alert_summary_widget/index.ts index aaeb820ff136b..9dbd13c9c8011 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/mock/alert_summary_widget/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/mock/alert_summary_widget/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertSummaryTimeRange } from '../../hooks/use_load_alert_summary'; +import { AlertSummaryTimeRange } from '../../sections/rule_details/components/alert_summary/types'; export const mockAlertSummaryResponse = { activeAlertCount: 2, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx index 5232eb2661966..0ecb70d1a478b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx @@ -41,6 +41,7 @@ export const AlertSummaryWidget = ({ activeAlerts={activeAlerts} recoveredAlertCount={recoveredAlertCount} recoveredAlerts={recoveredAlerts} + dateFormat={timeRange.dateFormat} /> ) : ( void; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_full_size.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_full_size.tsx index 4d8d7cc564e22..d338cd3a0668b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_full_size.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_full_size.tsx @@ -23,13 +23,14 @@ import { RECOVERED_COLOR, TOOLTIP_DATE_FORMAT, } from './constants'; -import { Alert } from '../../../../../hooks/use_load_alert_summary'; +import { Alert } from '../types'; export interface AlertsSummaryWidgetFullSizeProps { activeAlertCount: number; activeAlerts: Alert[]; recoveredAlertCount: number; recoveredAlerts: Alert[]; + dateFormat?: string; } export const AlertsSummaryWidgetFullSize = ({ @@ -37,6 +38,7 @@ export const AlertsSummaryWidgetFullSize = ({ activeAlerts, recoveredAlertCount, recoveredAlerts, + dateFormat, }: AlertsSummaryWidgetFullSizeProps) => { const isDarkMode = useUiSetting('theme:darkMode'); const { euiTheme } = useEuiTheme(); @@ -106,7 +108,8 @@ export const AlertsSummaryWidgetFullSize = ({ // TODO Use the EUI charts theme https://github.com/elastic/kibana/issues/148297 theme={chartTheme} tooltip={{ - headerFormatter: (tooltip) => moment(tooltip.value).format(TOOLTIP_DATE_FORMAT), + headerFormatter: (tooltip) => + moment(tooltip.value).format(dateFormat || TOOLTIP_DATE_FORMAT), }} />