Skip to content

Commit

Permalink
[ResponseOps][Window Maintenance] Add timezone field to the create fo…
Browse files Browse the repository at this point in the history
…rm (#155324)

Resolves #153977

## Summary

Adds the timezone combo box to the create form only if the Kibana
Setting is set to `'Browser'`.
When you edit a maintenance window the timezone will always be visible.

<img width="1376" alt="Screen Shot 2023-04-19 at 6 00 18 PM"
src="https://user-images.githubusercontent.com/109488926/233209410-da092529-d14f-430d-b6d5-9ac1471818a9.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 <[email protected]>
  • Loading branch information
doakalexi and kibanamachine authored Apr 20, 2023
1 parent 59f8635 commit 954d73d
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import {
CreateMaintenanceWindowFormProps,
CreateMaintenanceWindowForm,
} from './create_maintenance_windows_form';
import { useUiSetting } from '@kbn/kibana-react-plugin/public';

jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
useUiSetting: jest.fn(),
}));

const formProps: CreateMaintenanceWindowFormProps = {
onCancel: jest.fn(),
Expand All @@ -24,6 +29,7 @@ describe('CreateMaintenanceWindowForm', () => {
beforeEach(() => {
jest.clearAllMocks();
appMockRenderer = createAppMockRenderer();
(useUiSetting as jest.Mock).mockReturnValue('America/New_York');
});

it('renders all form fields except the recurring form fields', async () => {
Expand All @@ -33,6 +39,19 @@ describe('CreateMaintenanceWindowForm', () => {
expect(result.getByTestId('date-field')).toBeInTheDocument();
expect(result.getByTestId('recurring-field')).toBeInTheDocument();
expect(result.queryByTestId('recurring-form')).not.toBeInTheDocument();
expect(result.queryByTestId('timezone-field')).not.toBeInTheDocument();
});

it('renders timezone field when the kibana setting is set to browser', async () => {
(useUiSetting as jest.Mock).mockReturnValue('Browser');

const result = appMockRenderer.render(<CreateMaintenanceWindowForm {...formProps} />);

expect(result.getByTestId('title-field')).toBeInTheDocument();
expect(result.getByTestId('date-field')).toBeInTheDocument();
expect(result.getByTestId('recurring-field')).toBeInTheDocument();
expect(result.queryByTestId('recurring-form')).not.toBeInTheDocument();
expect(result.getByTestId('timezone-field')).toBeInTheDocument();
});

it('should initialize the form when no initialValue provided', () => {
Expand Down Expand Up @@ -60,6 +79,7 @@ describe('CreateMaintenanceWindowForm', () => {
title: 'test',
startDate: '2023-03-24',
endDate: '2023-03-26',
timezone: ['America/Los_Angeles'],
recurring: true,
}}
/>
Expand All @@ -71,10 +91,12 @@ describe('CreateMaintenanceWindowForm', () => {
'Press the down key to open a popover containing a calendar.'
);
const recurringInput = within(result.getByTestId('recurring-field')).getByTestId('input');
const timezoneInput = within(result.getByTestId('timezone-field')).getByTestId('input');

expect(titleInput).toHaveValue('test');
expect(dateInputs[0]).toHaveValue('03/24/2023 12:00 AM');
expect(dateInputs[1]).toHaveValue('03/26/2023 12:00 AM');
expect(recurringInput).toBeChecked();
expect(timezoneInput).toHaveTextContent('America/Los_Angeles');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@
import React, { useCallback, useState } from 'react';
import moment from 'moment';
import {
FIELD_TYPES,
Form,
getUseField,
useForm,
useFormData,
UseMultiFields,
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFormLabel,
EuiHorizontalRule,
} from '@elastic/eui';
import { TIMEZONE_OPTIONS as UI_TIMEZONE_OPTIONS } from '@kbn/core-ui-settings-common';

import { FormProps, schema } from './schema';
import * as i18n from '../translations';
Expand All @@ -35,16 +43,20 @@ export interface CreateMaintenanceWindowFormProps {
maintenanceWindowId?: string;
}

export const useTimeZone = (): string => {
const timeZone = useUiSetting<string>('dateFormat:tz');
return timeZone === 'Browser' ? moment.tz.guess() : timeZone;
const useDefaultTimezone = () => {
const kibanaTz: string = useUiSetting('dateFormat:tz');
if (!kibanaTz || kibanaTz === 'Browser') {
return { defaultTimezone: moment.tz?.guess() ?? 'UTC', isBrowser: true };
}
return { defaultTimezone: kibanaTz, isBrowser: false };
};
const TIMEZONE_OPTIONS = UI_TIMEZONE_OPTIONS.map((n) => ({ label: n })) ?? [{ label: 'UTC' }];

export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFormProps>(
({ onCancel, onSuccess, initialValue, maintenanceWindowId }) => {
const [defaultStartDateValue] = useState<string>(moment().toISOString());
const [defaultEndDateValue] = useState<string>(moment().add(30, 'minutes').toISOString());
const timezone = useTimeZone();
const { defaultTimezone, isBrowser } = useDefaultTimezone();

const isEditMode = initialValue !== undefined && maintenanceWindowId !== undefined;
const { mutate: createMaintenanceWindow, isLoading: isCreateLoading } =
Expand All @@ -60,7 +72,11 @@ export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFor
const maintenanceWindow = {
title: formData.title,
duration: endDate.diff(startDate),
rRule: convertToRRule(startDate, timezone, formData.recurringSchedule),
rRule: convertToRRule(
startDate,
formData.timezone ? formData.timezone[0] : defaultTimezone,
formData.recurringSchedule
),
};
if (isEditMode) {
updateMaintenanceWindow({ maintenanceWindowId, maintenanceWindow }, { onSuccess });
Expand All @@ -75,7 +91,7 @@ export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFor
updateMaintenanceWindow,
createMaintenanceWindow,
onSuccess,
timezone,
defaultTimezone,
]
);

Expand All @@ -91,10 +107,11 @@ export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFor
watch: ['recurring'],
});
const isRecurring = recurring || false;
const showTimezone = isBrowser || initialValue?.timezone !== undefined;

return (
<Form form={form}>
<EuiFlexGroup direction="column" gutterSize="l" responsive={false}>
<EuiFlexGroup direction="column" responsive={false}>
<EuiFlexItem>
<UseField
path="title"
Expand All @@ -107,50 +124,73 @@ export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFor
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<UseMultiFields
fields={{
startDate: {
path: 'startDate',
config: {
label: i18n.CREATE_FORM_SCHEDULE,
defaultValue: defaultStartDateValue,
validations: [],
},
},
endDate: {
path: 'endDate',
config: {
label: '',
defaultValue: defaultEndDateValue,
validations: [],
},
},
}}
>
{(fields) => (
<DatePickerRangeField fields={fields} data-test-subj="date-field" />
)}
</UseMultiFields>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<UseField
path="recurring"
componentProps={{
'data-test-subj': 'recurring-field',
<EuiFlexGroup alignItems="flexEnd" responsive={false}>
<EuiFlexItem grow={3}>
<UseMultiFields
fields={{
startDate: {
path: 'startDate',
config: {
label: i18n.CREATE_FORM_SCHEDULE,
defaultValue: defaultStartDateValue,
validations: [],
},
},
endDate: {
path: 'endDate',
config: {
label: '',
defaultValue: defaultEndDateValue,
validations: [],
},
},
}}
/>
</EuiFlexItem>
<EuiFlexItem>
{isRecurring ? <RecurringSchedule data-test-subj="recurring-form" /> : null}
>
{(fields) => <DatePickerRangeField fields={fields} data-test-subj="date-field" />}
</UseMultiFields>
</EuiFlexItem>
{showTimezone ? (
<EuiFlexItem grow={1}>
<UseField
path="timezone"
config={{
type: FIELD_TYPES.COMBO_BOX,
validations: [],
defaultValue: [defaultTimezone],
}}
componentProps={{
'data-test-subj': 'timezone-field',
id: 'timezone',
euiFieldProps: {
fullWidth: true,
options: TIMEZONE_OPTIONS,
singleSelection: { asPlainText: true },
isClearable: false,
noSuggestions: false,
placeholder: '',
prepend: (
<EuiFormLabel htmlFor={'timezone'}>
{i18n.CREATE_FORM_TIMEZONE}
</EuiFormLabel>
),
},
}}
/>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<UseField
path="recurring"
componentProps={{
'data-test-subj': 'recurring-field',
}}
/>
</EuiFlexItem>
<EuiFlexItem>
{isRecurring ? <RecurringSchedule data-test-subj="recurring-form" /> : null}
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule margin="xl" />
<EuiFlexGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
*/
import React, { useMemo } from 'react';
import moment from 'moment';
import { EuiFormLabel, EuiHorizontalRule, EuiSplitPanel } from '@elastic/eui';
import {
EuiComboBox,
EuiFlexGroup,
EuiFlexItem,
EuiFormLabel,
EuiHorizontalRule,
EuiSpacer,
EuiSplitPanel,
} from '@elastic/eui';
import { getUseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components';
import { getWeekdayInfo } from '../../helpers/get_weekday_info';
Expand All @@ -28,10 +36,11 @@ import { FormProps } from '../schema';
const UseField = getUseField({ component: Field });

export const RecurringSchedule: React.FC = React.memo(() => {
const [{ startDate, endDate, recurringSchedule }] = useFormData<FormProps>({
const [{ startDate, endDate, timezone, recurringSchedule }] = useFormData<FormProps>({
watch: [
'startDate',
'endDate',
'timezone',
'recurringSchedule.frequency',
'recurringSchedule.interval',
'recurringSchedule.ends',
Expand Down Expand Up @@ -104,19 +113,43 @@ export const RecurringSchedule: React.FC = React.memo(() => {
}}
/>
{recurringSchedule?.ends === EndsOptions.ON_DATE ? (
<UseField
path="recurringSchedule.until"
config={{
label: '',
defaultValue: moment(endDate).endOf('day').toISOString(),
validations: [],
}}
component={DatePickerField}
componentProps={{
'data-test-subj': 'until-field',
showTimeSelect: false,
}}
/>
<>
<EuiSpacer size="m" />
<EuiFlexGroup alignItems="flexEnd">
<EuiFlexItem grow={3}>
<UseField
path="recurringSchedule.until"
config={{
label: '',
defaultValue: moment(endDate).endOf('day').toISOString(),
validations: [],
}}
component={DatePickerField}
componentProps={{
'data-test-subj': 'until-field',
showTimeSelect: false,
}}
/>
</EuiFlexItem>
{timezone ? (
<EuiFlexItem grow={1}>
<EuiComboBox
data-test-subj="disabled-timezone-field"
id="disabled-timezone"
isDisabled
singleSelection={{ asPlainText: true }}
selectedOptions={[{ label: timezone[0] }]}
isClearable={false}
prepend={
<EuiFormLabel htmlFor={'disabled-timezone'}>
{i18n.CREATE_FORM_TIMEZONE}
</EuiFormLabel>
}
/>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</>
) : null}
{recurringSchedule?.ends === EndsOptions.AFTER_X ? (
<UseField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface FormProps {
title: string;
startDate: string;
endDate: string;
timezone?: string[];
recurring: boolean;
recurringSchedule?: RecurringScheduleFormProps;
}
Expand Down Expand Up @@ -45,6 +46,7 @@ export const schema: FormSchema<FormProps> = {
},
startDate: {},
endDate: {},
timezone: {},
recurring: {
type: FIELD_TYPES.TOGGLE,
label: i18n.CREATE_FORM_REPEAT,
Expand Down
Loading

0 comments on commit 954d73d

Please sign in to comment.