diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx index 33a36a2bc0dea4..9d4fc521c3f665 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx @@ -12,12 +12,15 @@ import { EuiBasicTableColumn, EuiButton, useEuiBackgroundColor, + EuiFlexGroup, + EuiFlexItem, SearchFilterConfig, } from '@elastic/eui'; import { css } from '@emotion/react'; import { MaintenanceWindowFindResponse, SortDirection } from '../types'; import * as i18n from '../translations'; import { useEditMaintenanceWindowsNavigation } from '../../../hooks/use_navigation'; +import { UpcomingEventsPopover } from './upcoming_events_popover'; import { StatusColor, STATUS_DISPLAY, STATUS_SORT } from '../constants'; import { MaintenanceWindowStatus } from '../../../../common'; import { StatusFilter } from './status_filter'; @@ -61,7 +64,18 @@ const columns: Array> = [ field: 'eventStartTime', name: i18n.TABLE_START_TIME, dataType: 'date', - render: (startDate: string) => formatDate(startDate, 'MM/DD/YY HH:mm A'), + render: (startDate: string, item: MaintenanceWindowFindResponse) => { + return ( + + {formatDate(startDate, 'MM/DD/YY HH:mm A')} + {item.events.length > 1 ? ( + + + + ) : null} + + ); + }, sortable: true, }, { diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx new file mode 100644 index 00000000000000..574bb7d1f7549c --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { fireEvent } from '@testing-library/react'; +import * as React from 'react'; +import { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils'; +import { UpcomingEventsPopover } from './upcoming_events_popover'; +import { MaintenanceWindowStatus } from '../../../../common'; + +describe('rule_actions_popover', () => { + let appMockRenderer: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + + it('renders the top 3 events', () => { + const result = appMockRenderer.render( + + ); + + const popoverButton = result.getByTestId('upcoming-events-icon-button'); + expect(popoverButton).toBeInTheDocument(); + fireEvent.click(popoverButton); + + expect(result.getByTestId('upcoming-events-popover-title')).toBeInTheDocument(); + expect(result.getByTestId('upcoming-events-popover-title')).toHaveTextContent( + 'Repeats every Friday' + ); + expect(result.getAllByTestId('upcoming-events-popover-item').length).toBe(3); + }); +}); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx new file mode 100644 index 00000000000000..a20907a2e12361 --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo, useState } from 'react'; +import moment from 'moment'; +import { findIndex } from 'lodash'; +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, + EuiText, + formatDate, +} from '@elastic/eui'; +import * as i18n from '../translations'; +import { recurringSummary } from '../helpers/recurring_summary'; +import { getPresets } from '../helpers/get_presets'; +import { MaintenanceWindowFindResponse } from '../types'; +import { convertFromMaintenanceWindowToForm } from '../helpers/convert_from_maintenance_window_to_form'; + +interface UpcomingEventsPopoverProps { + maintenanceWindowFindResponse: MaintenanceWindowFindResponse; +} + +export const UpcomingEventsPopover: React.FC = React.memo( + ({ maintenanceWindowFindResponse }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onButtonClick = useCallback(() => { + setIsPopoverOpen((open) => !open); + }, []); + const closePopover = useCallback(() => { + setIsPopoverOpen(false); + }, []); + + const { startDate, recurringSchedule, topEvents, presets } = useMemo(() => { + const maintenanceWindow = convertFromMaintenanceWindowToForm(maintenanceWindowFindResponse); + const date = moment(maintenanceWindow.startDate); + const currentEventIndex = findIndex( + maintenanceWindowFindResponse.events, + (event) => + event.gte === maintenanceWindowFindResponse.eventStartTime && + event.lte === maintenanceWindowFindResponse.eventEndTime + ); + return { + startDate: date, + recurringSchedule: maintenanceWindow.recurringSchedule, + topEvents: maintenanceWindowFindResponse.events.slice( + currentEventIndex + 1, + currentEventIndex + 4 + ), + presets: getPresets(date), + }; + }, [maintenanceWindowFindResponse]); + + return ( + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="downCenter" + > + + {i18n.CREATE_FORM_RECURRING_SUMMARY_PREFIX( + recurringSummary(startDate, recurringSchedule, presets) + )} + + + + + {i18n.UPCOMING} + + + + {topEvents.map((event, index) => ( + + + + + + + + {formatDate(event.gte, 'MM/DD/YY HH:mm A')} + + + + {index < topEvents.length - 1 ? ( + + ) : null} + + ))} + + + ); + } +); +UpcomingEventsPopover.displayName = 'UpcomingEventsPopover'; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts index fd2627d2478f1a..c892f7db667293 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts @@ -194,7 +194,7 @@ describe('convertFromMaintenanceWindowToForm', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 1, - bymonth: [2], + bymonth: [3], bymonthday: [22], }, }); @@ -307,7 +307,7 @@ describe('convertFromMaintenanceWindowToForm', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 3, - bymonth: [2], + bymonth: [3], bymonthday: [22], }, }); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts index 737bb47f4aa337..7eaecba5169b84 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts @@ -121,7 +121,7 @@ describe('convertToRRule', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 1, - bymonth: [2], + bymonth: [3], bymonthday: [22], }); }); @@ -209,7 +209,7 @@ describe('convertToRRule', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 3, - bymonth: [2], + bymonth: [3], bymonthday: [22], }); }); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts index b284e50579debb..90706165c717b2 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts @@ -68,7 +68,8 @@ export const convertToRRule = ( } if (frequency === Frequency.YEARLY) { - rRule.bymonth = [startDate.month()]; + // rRule expects 1 based indexing for months + rRule.bymonth = [startDate.month() + 1]; rRule.bymonthday = [startDate.date()]; } diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index 1b3317d9182b26..30b83963cc5b7c 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -468,3 +468,7 @@ export const EXPERIMENTAL_DESCRIPTION = i18n.translate( 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', } ); + +export const UPCOMING = i18n.translate('xpack.alerting.maintenanceWindows.upcoming', { + defaultMessage: 'Upcoming', +});