mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Co-authored-by: Dominique Clarke <doclarke71@gmail.com>
This commit is contained in:
parent
d3c7fe8829
commit
fdff04e46f
6 changed files with 263 additions and 3 deletions
|
@ -231,6 +231,8 @@ export type ICustomFields = HTTPFields &
|
|||
[ConfigKeys.NAME]: string;
|
||||
};
|
||||
|
||||
export type Monitor = Partial<ICustomFields>;
|
||||
|
||||
export interface PolicyConfig {
|
||||
[DataStream.HTTP]: HTTPFields;
|
||||
[DataStream.TCP]: TCPFields;
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import * as fetchers from '../../../state/api/monitor_management';
|
||||
import { DataStream, ScheduleUnit } from '../../fleet_package/types';
|
||||
import { ActionBar } from './action_bar';
|
||||
|
||||
describe('<ActionBar />', () => {
|
||||
const setMonitor = jest.spyOn(fetchers, 'setMonitor');
|
||||
const monitor = {
|
||||
name: 'test-monitor',
|
||||
schedule: {
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
number: '2',
|
||||
},
|
||||
urls: 'https://elastic.co',
|
||||
type: DataStream.BROWSER,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('only calls setMonitor when valid and after submission', () => {
|
||||
const id = 'test-id';
|
||||
render(<ActionBar monitor={monitor} id={id} isValid={true} />);
|
||||
|
||||
userEvent.click(screen.getByText('Edit monitor'));
|
||||
|
||||
expect(setMonitor).toBeCalledWith({ monitor, id });
|
||||
});
|
||||
|
||||
it('does not call setMonitor until submission', () => {
|
||||
const id = 'test-id';
|
||||
render(<ActionBar monitor={monitor} id={id} isValid={true} />);
|
||||
|
||||
expect(setMonitor).not.toBeCalled();
|
||||
|
||||
userEvent.click(screen.getByText('Edit monitor'));
|
||||
|
||||
expect(setMonitor).toBeCalledWith({ monitor, id });
|
||||
});
|
||||
|
||||
it('does not call setMonitor if invalid', () => {
|
||||
const id = 'test-id';
|
||||
render(<ActionBar monitor={monitor} id={id} isValid={false} />);
|
||||
|
||||
expect(setMonitor).not.toBeCalled();
|
||||
|
||||
userEvent.click(screen.getByText('Edit monitor'));
|
||||
|
||||
expect(setMonitor).not.toBeCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['', 'Save monitor'],
|
||||
['test-id', 'Edit monitor'],
|
||||
])('displays right call to action', (id, callToAction) => {
|
||||
render(<ActionBar monitor={monitor} id={id} isValid={true} />);
|
||||
|
||||
expect(screen.getByText(callToAction)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables button and displays help text when form is invalid after first submission', async () => {
|
||||
render(<ActionBar monitor={monitor} isValid={false} />);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Your monitor has errors. Please fix them before saving.')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Save monitor')).not.toBeDisabled();
|
||||
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Your monitor has errors. Please fix them before saving.')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Save monitor' })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls option onSave when saving monitor', () => {
|
||||
const onSave = jest.fn();
|
||||
render(<ActionBar monitor={monitor} isValid={false} onSave={onSave} />);
|
||||
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
|
||||
expect(onSave).toBeCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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, useState, useEffect } from 'react';
|
||||
import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../../observability/public';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import { setMonitor } from '../../../state/api';
|
||||
|
||||
import { Monitor } from '../../fleet_package/types';
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
monitor: Monitor;
|
||||
isValid: boolean;
|
||||
onSave?: () => void;
|
||||
}
|
||||
|
||||
export const ActionBar = ({ id, monitor, isValid, onSave }: Props) => {
|
||||
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const { notifications } = useKibana();
|
||||
|
||||
const { data, status } = useFetcher(() => {
|
||||
if (!isSaving || !isValid) {
|
||||
return;
|
||||
}
|
||||
return setMonitor({ monitor, id });
|
||||
}, [monitor, id, isValid, isSaving]);
|
||||
|
||||
const handleOnSave = useCallback(() => {
|
||||
if (onSave) {
|
||||
onSave();
|
||||
}
|
||||
setIsSaving(true);
|
||||
setHasBeenSubmitted(true);
|
||||
}, [onSave]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSaving) {
|
||||
return;
|
||||
}
|
||||
if (!isValid) {
|
||||
setIsSaving(false);
|
||||
return;
|
||||
}
|
||||
if (status === FETCH_STATUS.FAILURE || status === FETCH_STATUS.SUCCESS) {
|
||||
setIsSaving(false);
|
||||
}
|
||||
if (status === FETCH_STATUS.FAILURE) {
|
||||
notifications.toasts.danger({
|
||||
title: <p data-test-subj="uptimeAddMonitorFailure">{MONITOR_FAILURE_LABEL}</p>,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
} else if (status === FETCH_STATUS.SUCCESS) {
|
||||
notifications.toasts.success({
|
||||
title: <p data-test-subj="uptimeAddMonitorSuccess">{MONITOR_SUCCESS_LABEL}</p>,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
}
|
||||
}, [data, status, notifications.toasts, isSaving, isValid]);
|
||||
|
||||
return (
|
||||
<EuiBottomBar>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>{!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" size="s" iconType="cross">
|
||||
{DISCARD_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
size="s"
|
||||
iconType="check"
|
||||
onClick={handleOnSave}
|
||||
isLoading={isSaving}
|
||||
disabled={hasBeenSubmitted && !isValid}
|
||||
>
|
||||
{id ? EDIT_MONITOR_LABEL : SAVE_MONITOR_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
);
|
||||
};
|
||||
|
||||
const DISCARD_LABEL = i18n.translate('xpack.uptime.monitorManagement.discardLabel', {
|
||||
defaultMessage: 'Discard',
|
||||
});
|
||||
|
||||
const SAVE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.saveMonitorLabel', {
|
||||
defaultMessage: 'Save monitor',
|
||||
});
|
||||
|
||||
const EDIT_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.editMonitorLabel', {
|
||||
defaultMessage: 'Edit monitor',
|
||||
});
|
||||
|
||||
const VALIDATION_ERROR_LABEL = i18n.translate('xpack.uptime.monitorManagement.validationError', {
|
||||
defaultMessage: 'Your monitor has errors. Please fix them before saving.',
|
||||
});
|
||||
|
||||
const MONITOR_SUCCESS_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.monitorSuccessMessage',
|
||||
{
|
||||
defaultMessage: 'Monitor added successfully.',
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Discuss error states with product
|
||||
const MONITOR_FAILURE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.monitorFailureMessage',
|
||||
{
|
||||
defaultMessage: 'Monitor was unable to be saved. Please try again later.',
|
||||
}
|
||||
);
|
|
@ -12,9 +12,14 @@ import { defaultConfig, usePolicyConfigContext } from '../fleet_package/contexts
|
|||
import { usePolicy } from '../fleet_package/hooks/use_policy';
|
||||
import { validate } from '../fleet_package/validation';
|
||||
import { MonitorFields } from './monitor_fields';
|
||||
import { ActionBar } from './action_bar/action_bar';
|
||||
import { useFormatMonitor } from './hooks/use_format_monitor';
|
||||
|
||||
export const MonitorConfig = () => {
|
||||
interface Props {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const MonitorConfig = ({ id }: Props) => {
|
||||
const { monitorType } = usePolicyConfigContext();
|
||||
/* TODO - Use Effect to make sure the package/index templates are loaded. Wait for it to load before showing view
|
||||
* then show error message if it fails */
|
||||
|
@ -26,12 +31,17 @@ export const MonitorConfig = () => {
|
|||
This type of helper should ideally be moved to task manager where we are syncing the config.
|
||||
We can process validation (isValid) and formatting for heartbeat (formattedMonitor) separately
|
||||
We don't need to save the heartbeat compatible version in saved objects */
|
||||
useFormatMonitor({
|
||||
const { isValid } = useFormatMonitor({
|
||||
monitorType,
|
||||
validate,
|
||||
config: policyConfig[monitorType],
|
||||
defaultConfig: defaultConfig[monitorType],
|
||||
});
|
||||
|
||||
return <MonitorFields />;
|
||||
return (
|
||||
<>
|
||||
<MonitorFields />
|
||||
<ActionBar id={id} monitor={policyConfig[monitorType]} isValid={isValid} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,3 +13,4 @@ export * from './dynamic_settings';
|
|||
export * from './index_status';
|
||||
export * from './ping';
|
||||
export * from './monitor_duration';
|
||||
export * from './monitor_management';
|
||||
|
|
18
x-pack/plugins/uptime/public/state/api/monitor_management.ts
Normal file
18
x-pack/plugins/uptime/public/state/api/monitor_management.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { API_URLS } from '../../../common/constants';
|
||||
import { apiService } from './utils';
|
||||
|
||||
// TODO, change to monitor runtime type
|
||||
export const setMonitor = async ({ monitor, id }: { monitor: any; id?: string }): Promise<void> => {
|
||||
if (id) {
|
||||
return await apiService.post(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor);
|
||||
} else {
|
||||
return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor);
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue