Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,8 @@ export function monitorManagementPageProvider({
},

async selectLocations({ locations }: { locations: string[] }) {
await this.clickByTestSubj('syntheticsServiceLocationsComboBox');
for (let i = 0; i < locations.length; i++) {
await page.click(`text=${locations[i]}`);
await page.check(`text=${locations[i]}`);
}
},

Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/uptime/e2e/page_objects/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export function utilsPageProvider({ page }: { page: Page }) {
await page.selectOption(`[data-test-subj=${dataTestSubj}]`, value);
},

async checkByTestSubj(dataTestSubj: string, value: string) {
await page.check(`[data-test-subj=${dataTestSubj}]`);
},

async clickByTestSubj(dataTestSubj: string) {
await page.click(`[data-test-subj=${dataTestSubj}]`);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { render } from '../../../lib/helper/rtl_helpers';
import { ServiceLocations, LOCATIONS_LABEL } from './locations';
import userEvent from '@testing-library/user-event';
import { ServiceLocations } from './locations';

describe('<ActionBar />', () => {
const setLocations = jest.fn();
Expand Down Expand Up @@ -52,47 +51,7 @@ describe('<ActionBar />', () => {
{ state }
);

expect(screen.getByText(LOCATIONS_LABEL)).toBeInTheDocument();
expect(screen.queryByText('US Central')).not.toBeInTheDocument();
});

it('shows location options when clicked', async () => {
render(
<ServiceLocations selectedLocations={[]} setLocations={setLocations} isInvalid={false} />,
{ state }
);

userEvent.click(screen.getByRole('button'));

expect(screen.getByText('US Central')).toBeInTheDocument();
});

it('prevents bad inputs', async () => {
render(
<ServiceLocations selectedLocations={[]} setLocations={setLocations} isInvalid={false} />,
{ state }
);

userEvent.click(screen.getByRole('button'));
userEvent.type(screen.getByRole('textbox'), 'fake location');

expect(screen.getByText("doesn't match any options")).toBeInTheDocument();

userEvent.keyboard(`{enter}`);

expect(screen.getByText('"fake location" is not a valid option')).toBeInTheDocument();
});

it('calls setLocations', async () => {
render(
<ServiceLocations selectedLocations={[]} setLocations={setLocations} isInvalid={false} />,
{ state }
);

userEvent.click(screen.getByRole('button'));
userEvent.click(screen.getByText('US Central'));

expect(setLocations).toBeCalledWith([location]);
expect(screen.queryByText('US Central')).toBeInTheDocument();
});

it('shows invalid error', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* 2.0.
*/

import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
import { EuiCheckboxGroup, EuiFormRow } from '@elastic/eui';
import { monitorManagementListSelector } from '../../../state/selectors';
import { ServiceLocation } from '../../../../common/runtime_types';

Expand All @@ -20,66 +20,56 @@ interface Props {

export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid }: Props) => {
const [error, setError] = useState<string | null>(null);
const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState<Record<string, boolean>>(
{}
);
const { locations } = useSelector(monitorManagementListSelector);

const onLocationChange = (
selectedLocationOptions: Array<EuiComboBoxOptionOption<ServiceLocation>>
) => {
setLocations(selectedLocationOptions as ServiceLocation[]);
setError(null);
};

const onSearchChange = (value: string, hasMatchingOptions?: boolean) => {
setError(value.length === 0 || hasMatchingOptions ? null : getInvalidOptionError(value));
};

const onBlur = (event: unknown) => {
const inputElement = (event as FocusEvent)?.target as HTMLInputElement;
if (inputElement) {
const { value } = inputElement;
setError(value.length === 0 ? null : getInvalidOptionError(value));
const onLocationChange = (optionId: string) => {
const isSelected = !checkboxIdToSelectedMap[optionId];
const location = locations.find((loc) => loc.id === optionId);
if (isSelected) {
setLocations((prevLocations) => (location ? [...prevLocations, location] : prevLocations));
} else {
setLocations((prevLocations) => [...prevLocations].filter((loc) => loc.id !== optionId));
}
setError(null);
};

const errorMessage = error ?? (isInvalid ? VALIDATION_ERROR : null);

useEffect(() => {
const newCheckboxIdToSelectedMap = selectedLocations.reduce<Record<string, boolean>>(
(acc, location) => {
acc[location.id] = true;
return acc;
},
{}
);
setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap);
}, [selectedLocations]);

return (
<EuiFormRow label={LOCATIONS_LABEL} error={errorMessage} isInvalid={errorMessage !== null}>
<EuiComboBox
placeholder={PLACEHOLDER_LABEL}
options={locations}
selectedOptions={selectedLocations}
onChange={onLocationChange}
onSearchChange={onSearchChange}
onBlur={onBlur}
data-test-subj="syntheticsServiceLocationsComboBox"
<EuiCheckboxGroup
options={locations.map((location) => ({
...location,
'data-test-subj': `syntheticsServiceLocation--${location.id}`,
}))}
idToSelectedMap={checkboxIdToSelectedMap}
onChange={(id) => onLocationChange(id)}
/>
</EuiFormRow>
);
};

const PLACEHOLDER_LABEL = i18n.translate(
'xpack.uptime.monitorManagement.serviceLocationsPlaceholderLabel',
{
defaultMessage: 'Select one or more locations to run your monitor.',
}
);

const VALIDATION_ERROR = i18n.translate(
'xpack.uptime.monitorManagement.serviceLocationsValidationError',
{
defaultMessage: 'At least one service location must be specified',
}
);

const getInvalidOptionError = (value: string) =>
i18n.translate('xpack.uptime.monitorManagement.serviceLocationsOptionError', {
defaultMessage: '"{value}" is not a valid option',
values: {
value,
},
});

export const LOCATIONS_LABEL = i18n.translate(
'xpack.uptime.monitorManagement.monitorLocationsLabel',
{
Expand Down