Skip to content

Commit

Permalink
[ResponseOps][Window Maintenance] Add the upcoming events popover to …
Browse files Browse the repository at this point in the history
…the maintenance window table (#154978)

Resolves #154815

## Summary

Adding upcoming popover
<img width="1707" alt="Screen Shot 2023-04-14 at 2 27 58 PM"
src="https://user-images.githubusercontent.com/109488926/232127308-3db07e03-7ba5-4e05-851a-b2176632c7d3.png">


### Checklist

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Lisa Cawley <lcawley@elastic.co>
  • Loading branch information
3 people authored Apr 20, 2023
1 parent 954d73d commit 1676432
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -61,7 +64,18 @@ const columns: Array<EuiBasicTableColumn<MaintenanceWindowFindResponse>> = [
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 (
<EuiFlexGroup responsive={false} alignItems="center">
<EuiFlexItem grow={false}>{formatDate(startDate, 'MM/DD/YY HH:mm A')}</EuiFlexItem>
{item.events.length > 1 ? (
<EuiFlexItem grow={false}>
<UpcomingEventsPopover maintenanceWindowFindResponse={item} />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
);
},
sortable: true,
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<UpcomingEventsPopover
maintenanceWindowFindResponse={{
title: 'test MW',
enabled: true,
duration: 0,
events: [
{ gte: '2023-04-14T14:58:40.243Z', lte: '2023-04-14T14:58:40.243Z' },
{ gte: '2023-04-21T14:58:40.243Z', lte: '2023-04-21T14:58:40.243Z' },
{ gte: '2023-04-28T14:58:40.243Z', lte: '2023-04-28T14:58:40.243Z' },
{ gte: '2023-05-05T14:58:40.243Z', lte: '2023-05-05T14:58:40.243Z' },
{ gte: '2023-05-12T14:58:40.243Z', lte: '2023-05-12T14:58:40.243Z' },
{ gte: '2023-05-19T14:58:40.243Z', lte: '2023-05-19T14:58:40.243Z' },
],
id: 'dccedda0-dad4-11ed-9f8d-2b13e6c2138e',
status: MaintenanceWindowStatus.Upcoming,
expirationDate: '2024-04-14T14:58:58.997Z',
rRule: {
dtstart: '2023-04-14T14:58:40.243Z',
tzid: 'America/New_York',
freq: 3,
interval: 1,
byweekday: ['FR'],
},
createdBy: 'elastic',
updatedBy: 'elastic',
createdAt: '2023-04-14T14:58:58.997Z',
updatedAt: '2023-04-14T14:58:58.997Z',
eventStartTime: '2023-04-21T14:58:40.243Z',
eventEndTime: '2023-04-21T14:58:40.243Z',
total: 1000,
}}
/>
);

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);
});
});
Original file line number Diff line number Diff line change
@@ -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<UpcomingEventsPopoverProps> = 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 (
<EuiPopover
button={
<EuiButtonIcon
data-test-subj="upcoming-events-icon-button"
color="text"
display="base"
iconType="calendar"
size="s"
aria-label="Upcoming events"
onClick={onButtonClick}
/>
}
isOpen={isPopoverOpen}
closePopover={closePopover}
anchorPosition="downCenter"
>
<EuiPopoverTitle data-test-subj="upcoming-events-popover-title">
{i18n.CREATE_FORM_RECURRING_SUMMARY_PREFIX(
recurringSummary(startDate, recurringSchedule, presets)
)}
</EuiPopoverTitle>
<EuiFlexGroup direction="column" responsive={false} gutterSize="none">
<EuiFlexItem grow={false}>
<EuiText style={{ fontWeight: 700 }} color="subdued" size="s">
{i18n.UPCOMING}
</EuiText>
<EuiSpacer size="m" />
</EuiFlexItem>
{topEvents.map((event, index) => (
<EuiFlexItem
data-test-subj="upcoming-events-popover-item"
key={`startDate.${index}`}
grow={false}
>
<EuiFlexGroup
responsive={false}
alignItems="center"
justifyContent="flexStart"
style={{ width: '300px' }}
>
<EuiFlexItem grow={false}>
<EuiIcon color="subdued" type="calendar" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="s">
{formatDate(event.gte, 'MM/DD/YY HH:mm A')}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
{index < topEvents.length - 1 ? (
<EuiHorizontalRule
style={{ inlineSize: 'unset', marginInline: '-16px' }}
margin="s"
/>
) : null}
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiPopover>
);
}
);
UpcomingEventsPopover.displayName = 'UpcomingEventsPopover';
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
tzid: 'UTC',
freq: RRuleFrequency.YEARLY,
interval: 1,
bymonth: [2],
bymonth: [3],
bymonthday: [22],
},
});
Expand Down Expand Up @@ -307,7 +307,7 @@ describe('convertFromMaintenanceWindowToForm', () => {
tzid: 'UTC',
freq: RRuleFrequency.YEARLY,
interval: 3,
bymonth: [2],
bymonth: [3],
bymonthday: [22],
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('convertToRRule', () => {
tzid: 'UTC',
freq: RRuleFrequency.YEARLY,
interval: 1,
bymonth: [2],
bymonth: [3],
bymonthday: [22],
});
});
Expand Down Expand Up @@ -209,7 +209,7 @@ describe('convertToRRule', () => {
tzid: 'UTC',
freq: RRuleFrequency.YEARLY,
interval: 3,
bymonth: [2],
bymonth: [3],
bymonthday: [22],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});

0 comments on commit 1676432

Please sign in to comment.