mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Synthetics] Public API's refactor (#190531)
## Summary Public API's refactor !! Fixes https://github.com/elastic/kibana/issues/189906 !! ### Testing Please test HTTP, TCP, ICMP and Browser monitors creation and updates via API ---------------- ### params field return `params` always as an object `{}` ---------------- ### `ssl` field ssl field isn't returned anymore for browser monitor and is returned always as an object for other monitor types as in ``` "ssl": { "certificate_authorities": "", "certificate": "", "key": "i am a key", "key_passphrase": "", "verification_mode": "full", "supported_protocols": [ "TLSv1.1", "TLSv1.2", "TLSv1.3" ] }, ``` ---------------- ### `response` field also returned as nested object ``` "response": { "include_body": "on_error", "include_headers": true, "include_body_max_bytes": "1024" }, ``` ----- ### `check` field is also returned as nested object now ``` "check": { "response.body.negative": [], "response.body.positive": [], "response.json": [], "response.headers": {}, "response.status": [], "request.body": { "value": "", "type": "text" }, "request.headers": {}, "request.method": "GET" } ``` ----- ### `retest_on_failure` retest_on_failure is always returned now, max_attempts abstraction is omitted ----- ### `max_redirects` always return `max_redirects` as a number --------- Co-authored-by: Justin Kambic <jk@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
ca61209d13
commit
a333373d9e
50 changed files with 769 additions and 444 deletions
|
@ -173,7 +173,8 @@ export type ICMPFields = t.TypeOf<typeof ICMPFieldsCodec>;
|
|||
export const HTTPSimpleFieldsCodec = t.intersection([
|
||||
t.interface({
|
||||
[ConfigKey.METADATA]: MetadataCodec,
|
||||
[ConfigKey.MAX_REDIRECTS]: t.string,
|
||||
// string is for yaml config and number for public api
|
||||
[ConfigKey.MAX_REDIRECTS]: t.union([t.string, t.number]),
|
||||
[ConfigKey.URLS]: getNonEmptyStringCodec('url'),
|
||||
[ConfigKey.PORT]: t.union([t.number, t.null]),
|
||||
}),
|
||||
|
@ -318,6 +319,11 @@ export const MonitorFieldsCodec = t.intersection([
|
|||
BrowserFieldsCodec,
|
||||
]);
|
||||
|
||||
export const MonitorFieldsResultCodec = t.intersection([
|
||||
MonitorFieldsCodec,
|
||||
t.interface({ id: t.string, updated_at: t.string, created_at: t.string }),
|
||||
]);
|
||||
|
||||
// Monitor, represents one of (Icmp | Tcp | Http | Browser) decrypted
|
||||
export const SyntheticsMonitorCodec = t.union([
|
||||
HTTPFieldsCodec,
|
||||
|
@ -336,7 +342,7 @@ export const EncryptedSyntheticsMonitorCodec = t.union([
|
|||
|
||||
export const SyntheticsMonitorWithIdCodec = t.intersection([
|
||||
SyntheticsMonitorCodec,
|
||||
t.interface({ id: t.string }),
|
||||
t.interface({ id: t.string, updated_at: t.string, created_at: t.string }),
|
||||
]);
|
||||
|
||||
const HeartbeatFieldsCodec = t.intersection([
|
||||
|
@ -355,7 +361,10 @@ const HeartbeatFieldsCodec = t.intersection([
|
|||
]);
|
||||
|
||||
export const HeartbeatConfigCodec = t.intersection([
|
||||
SyntheticsMonitorWithIdCodec,
|
||||
SyntheticsMonitorCodec,
|
||||
t.interface({
|
||||
id: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
fields_under_root: t.boolean,
|
||||
fields: HeartbeatFieldsCodec,
|
||||
|
@ -400,6 +409,7 @@ export type BrowserFields = t.TypeOf<typeof BrowserFieldsCodec>;
|
|||
export type BrowserSimpleFields = t.TypeOf<typeof BrowserSimpleFieldsCodec>;
|
||||
export type BrowserAdvancedFields = t.TypeOf<typeof BrowserAdvancedFieldsCodec>;
|
||||
export type MonitorFields = t.TypeOf<typeof MonitorFieldsCodec>;
|
||||
export type MonitorFieldsResult = t.TypeOf<typeof MonitorFieldsResultCodec>;
|
||||
export type HeartbeatFields = t.TypeOf<typeof HeartbeatFieldsCodec>;
|
||||
export type SyntheticsMonitor = t.TypeOf<typeof SyntheticsMonitorCodec>;
|
||||
export type SyntheticsMonitorWithId = t.TypeOf<typeof SyntheticsMonitorWithIdCodec>;
|
||||
|
|
|
@ -62,7 +62,7 @@ export const ProjectMonitorCodec = t.intersection([
|
|||
hash: t.string,
|
||||
namespace: t.string,
|
||||
retestOnFailure: t.boolean,
|
||||
labels: t.record(t.string, t.string),
|
||||
fields: t.record(t.string, t.string),
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -91,14 +91,10 @@ export const ProjectMonitorsResponseCodec = t.intersection([
|
|||
}),
|
||||
]);
|
||||
|
||||
export type ProjectMonitorThrottlingConfig = t.TypeOf<typeof ProjectMonitorThrottlingConfigCodec>;
|
||||
|
||||
export type ProjectMonitor = t.TypeOf<typeof ProjectMonitorCodec>;
|
||||
|
||||
export type LegacyProjectMonitorsRequest = t.TypeOf<typeof LegacyProjectMonitorsRequestCodec>;
|
||||
|
||||
export type ProjectMonitorsRequest = t.TypeOf<typeof ProjectMonitorsRequestCodec>;
|
||||
|
||||
export type ProjectMonitorsResponse = t.TypeOf<typeof ProjectMonitorsResponseCodec>;
|
||||
|
||||
export type ProjectMonitorMetaData = t.TypeOf<typeof ProjectMonitorMetaDataCodec>;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { CA_CERT_PATH } from '@kbn/dev-utils';
|
|||
import { get } from 'lodash';
|
||||
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||
import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services';
|
||||
|
||||
import { readKibanaConfig } from './tasks/read_kibana_config';
|
||||
const MANIFEST_KEY = 'xpack.uptime.service.manifestUrl';
|
||||
const SERVICE_PASSWORD = 'xpack.uptime.service.password';
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import { after, before, expect, journey, step } from '@elastic/synthetics';
|
||||
import { omit } from 'lodash';
|
||||
import { SyntheticsMonitor } from '@kbn/synthetics-plugin/common/runtime_types';
|
||||
import { SyntheticsServices } from './services/synthetics_services';
|
||||
import { cleanTestMonitors, enableMonitorManagedViaApi } from './services/add_monitor';
|
||||
import { cleanTestMonitors } from './services/add_monitor';
|
||||
import { addTestMonitorProject } from './services/add_monitor_project';
|
||||
import { syntheticsAppPageProvider } from '../page_objects/synthetics_app';
|
||||
|
||||
|
@ -23,14 +24,13 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => {
|
|||
|
||||
before(async () => {
|
||||
await cleanTestMonitors(params);
|
||||
await enableMonitorManagedViaApi(params.kibanaUrl);
|
||||
|
||||
await addTestMonitorProject(params.kibanaUrl, monitorName);
|
||||
|
||||
await syntheticsApp.waitForLoadingToFinish();
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await addTestMonitorProject(params.kibanaUrl, monitorName);
|
||||
|
||||
await syntheticsApp.waitForLoadingToFinish();
|
||||
|
||||
await syntheticsApp.navigateToMonitorManagement();
|
||||
});
|
||||
|
||||
|
@ -62,11 +62,16 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => {
|
|||
// hash is always reset to empty string when monitor is edited
|
||||
// this ensures that when the monitor is pushed again, the monitor
|
||||
// config in the process takes precedence
|
||||
expect(newConfiguration).toEqual({
|
||||
...originalMonitorConfiguration,
|
||||
hash: '',
|
||||
revision: 2,
|
||||
});
|
||||
expect(omit(newConfiguration, ['updated_at'])).toEqual(
|
||||
omit(
|
||||
{
|
||||
...originalMonitorConfiguration,
|
||||
hash: '',
|
||||
revision: 2,
|
||||
},
|
||||
['updated_at']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
step('Navigate to edit monitor', async () => {
|
||||
|
@ -83,29 +88,39 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => {
|
|||
// hash is always reset to empty string when monitor is edited
|
||||
// this ensures that when the monitor is pushed again, the monitor
|
||||
// config in the process takes precedence
|
||||
expect(newConfiguration).toEqual({
|
||||
...originalMonitorConfiguration,
|
||||
hash: '',
|
||||
revision: 3,
|
||||
alert: {
|
||||
status: {
|
||||
enabled: !(originalMonitorConfiguration?.alert?.status?.enabled as boolean),
|
||||
expect(omit(newConfiguration, ['updated_at'])).toEqual(
|
||||
omit(
|
||||
{
|
||||
...originalMonitorConfiguration,
|
||||
hash: '',
|
||||
revision: 3,
|
||||
alert: {
|
||||
status: {
|
||||
enabled: !(originalMonitorConfiguration?.alert?.status?.enabled as boolean),
|
||||
},
|
||||
tls: {
|
||||
enabled: originalMonitorConfiguration?.alert?.tls?.enabled as boolean,
|
||||
},
|
||||
},
|
||||
enabled: !originalMonitorConfiguration?.enabled,
|
||||
},
|
||||
tls: {
|
||||
enabled: originalMonitorConfiguration?.alert?.tls?.enabled as boolean,
|
||||
},
|
||||
},
|
||||
enabled: !originalMonitorConfiguration?.enabled,
|
||||
});
|
||||
['updated_at']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
step('Monitor can be re-pushed and overwrite any changes', async () => {
|
||||
await addTestMonitorProject(params.kibanaUrl, monitorName);
|
||||
const repushedConfiguration = await services.getMonitor(monitorId);
|
||||
expect(repushedConfiguration).toEqual({
|
||||
...originalMonitorConfiguration,
|
||||
revision: 4,
|
||||
});
|
||||
expect(omit(repushedConfiguration, ['updated_at'])).toEqual(
|
||||
omit(
|
||||
{
|
||||
...originalMonitorConfiguration,
|
||||
revision: 4,
|
||||
},
|
||||
['updated_at']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
step('Navigate to edit monitor', async () => {
|
||||
|
|
|
@ -17,23 +17,15 @@ export const addTestMonitorProject = async (
|
|||
const testData = {
|
||||
...testProjectMonitorBrowser(name, config),
|
||||
};
|
||||
try {
|
||||
return await axios.put(
|
||||
kibanaUrl +
|
||||
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
|
||||
'{projectName}',
|
||||
projectName
|
||||
),
|
||||
testData,
|
||||
{
|
||||
auth: { username: 'elastic', password: 'changeme' },
|
||||
headers: { 'kbn-xsrf': 'true', 'x-elastic-internal-origin': 'synthetics-e2e' },
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(e);
|
||||
}
|
||||
return await axios.put(
|
||||
kibanaUrl +
|
||||
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', projectName),
|
||||
testData,
|
||||
{
|
||||
auth: { username: 'elastic', password: 'changeme' },
|
||||
headers: { 'kbn-xsrf': 'true', 'x-elastic-internal-origin': 'synthetics-e2e' },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const testProjectMonitorBrowser = (name: string, config?: Record<string, unknown>) => ({
|
||||
|
|
|
@ -28,10 +28,9 @@ export class SyntheticsServices {
|
|||
try {
|
||||
const { data } = await this.requester.request({
|
||||
description: 'get monitor by id',
|
||||
path: SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId),
|
||||
query: {
|
||||
decrypted: true,
|
||||
},
|
||||
path:
|
||||
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId) +
|
||||
'?internal=true',
|
||||
method: 'GET',
|
||||
});
|
||||
return data as SyntheticsMonitor;
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
EncryptedSyntheticsSavedMonitor,
|
||||
MonitorFields,
|
||||
Ping,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../../../../common/runtime_types';
|
||||
import { MonitorTypeBadge } from './monitor_type_badge';
|
||||
import { useDateFormat } from '../../../../../hooks/use_date_format';
|
||||
|
@ -36,7 +37,7 @@ export interface MonitorDetailsPanelProps {
|
|||
latestPing?: Ping;
|
||||
loading: boolean;
|
||||
configId: string;
|
||||
monitor: EncryptedSyntheticsSavedMonitor | null;
|
||||
monitor: SyntheticsMonitorWithId | EncryptedSyntheticsSavedMonitor | null;
|
||||
hideEnabled?: boolean;
|
||||
hideLocations?: boolean;
|
||||
hasBorder?: boolean;
|
||||
|
|
|
@ -15,7 +15,12 @@ import { LastRefreshed } from '../components/last_refreshed';
|
|||
import { AutoRefreshButton } from '../components/auto_refresh_button';
|
||||
import { useSyntheticsSettingsContext } from '../../../contexts';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
import { MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../../../common/constants';
|
||||
import {
|
||||
MONITOR_ADD_ROUTE,
|
||||
MONITOR_EDIT_ROUTE,
|
||||
MONITOR_ROUTE,
|
||||
SETTINGS_ROUTE,
|
||||
} from '../../../../../../common/constants';
|
||||
import { stringifyUrlParams } from '../../../utils/url_params';
|
||||
import { InspectorHeaderLink } from './inspector_header_link';
|
||||
import { ToggleAlertFlyoutButton } from '../../alerts/toggle_alert_flyout_button';
|
||||
|
@ -41,6 +46,8 @@ export function ActionMenuContent(): React.ReactElement {
|
|||
}; /* useSelector(monitorStatusSelector) TODO: Implement state for monitor status */
|
||||
|
||||
const detailRouteMatch = useRouteMatch(MONITOR_ROUTE);
|
||||
const isEditRoute = useRouteMatch(MONITOR_EDIT_ROUTE);
|
||||
const isAddRoute = useRouteMatch(MONITOR_ADD_ROUTE);
|
||||
const monitorId = selectedMonitor?.monitor?.id;
|
||||
|
||||
const syntheticExploratoryViewLink = createExploratoryViewUrl(
|
||||
|
@ -69,8 +76,12 @@ export function ActionMenuContent(): React.ReactElement {
|
|||
|
||||
return (
|
||||
<EuiHeaderLinks gutterSize="xs">
|
||||
<LastRefreshed />
|
||||
<AutoRefreshButton />
|
||||
{!isEditRoute && !isAddRoute && (
|
||||
<>
|
||||
<LastRefreshed />
|
||||
<AutoRefreshButton />
|
||||
</>
|
||||
)}
|
||||
<ToggleAlertFlyoutButton />
|
||||
|
||||
<EuiHeaderLink
|
||||
|
|
|
@ -70,6 +70,26 @@ export const formatDefaultFormValues = (monitor?: SyntheticsMonitor) => {
|
|||
schedule.number = `${schedule.number}s`;
|
||||
}
|
||||
|
||||
const params = monitorWithFormMonitorType[ConfigKey.PARAMS];
|
||||
if (typeof params !== 'string' && params) {
|
||||
try {
|
||||
monitorWithFormMonitorType[ConfigKey.PARAMS] = JSON.stringify(params);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
const browserMonitor = monitor as BrowserFields;
|
||||
|
||||
const pwOptions = browserMonitor[ConfigKey.PLAYWRIGHT_OPTIONS];
|
||||
if (typeof pwOptions !== 'string' && pwOptions) {
|
||||
try {
|
||||
(monitorWithFormMonitorType as BrowserFields)[ConfigKey.PLAYWRIGHT_OPTIONS] =
|
||||
JSON.stringify(pwOptions);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// handle default monitor types from Uptime, which don't contain `ConfigKey.FORM_MONITOR_TYPE`
|
||||
if (!formMonitorType) {
|
||||
formMonitorType =
|
||||
|
@ -81,7 +101,6 @@ export const formatDefaultFormValues = (monitor?: SyntheticsMonitor) => {
|
|||
|
||||
switch (formMonitorType) {
|
||||
case FormMonitorType.MULTISTEP:
|
||||
const browserMonitor = monitor as BrowserFields;
|
||||
return {
|
||||
...monitorWithFormMonitorType,
|
||||
'source.inline': {
|
||||
|
|
|
@ -148,6 +148,7 @@ export const BROWSER_ADVANCED = (readOnly: boolean) => [
|
|||
FIELD(readOnly)[ConfigKey.IGNORE_HTTPS_ERRORS],
|
||||
FIELD(readOnly)[ConfigKey.SYNTHETICS_ARGS],
|
||||
FIELD(readOnly)[ConfigKey.PLAYWRIGHT_OPTIONS],
|
||||
FIELD(readOnly)[ConfigKey.PARAMS],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -117,8 +117,7 @@ const validateHTTP: ValidationLibrary = {
|
|||
return validateHeaders(headers);
|
||||
},
|
||||
[ConfigKey.MAX_REDIRECTS]: ({ [ConfigKey.MAX_REDIRECTS]: value }) =>
|
||||
(!!value && !`${value}`.match(DIGITS_ONLY)) ||
|
||||
parseFloat(value as MonitorFields[ConfigKey.MAX_REDIRECTS]) < 0,
|
||||
(!!value && !`${value}`.match(DIGITS_ONLY)) || parseFloat(value as string) < 0,
|
||||
[ConfigKey.URLS]: ({ [ConfigKey.URLS]: value }) => !value,
|
||||
...validateCommon,
|
||||
};
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
*/
|
||||
|
||||
import { useFetcher } from '@kbn/observability-shared-plugin/public';
|
||||
import { fetchSyntheticsMonitor } from '../../../state/monitor_details/api';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
import { getDecryptedMonitorAPI } from '../../../state/monitor_management/api';
|
||||
|
||||
export const useCloneMonitor = () => {
|
||||
const { cloneId } = useGetUrlParams();
|
||||
return useFetcher(() => {
|
||||
if (!cloneId) return Promise.resolve(undefined);
|
||||
return getDecryptedMonitorAPI({ id: cloneId });
|
||||
return fetchSyntheticsMonitor({ monitorId: cloneId });
|
||||
}, [cloneId]);
|
||||
};
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
|
||||
import { useGetUrlParams, useUrlParams } from '../../../hooks';
|
||||
import { IHttpSerializedFetchError } from '../../../state';
|
||||
|
||||
export const useMonitorNotFound = (error?: IHttpFetchError<ResponseErrorBody>, id?: string) => {
|
||||
export const useMonitorNotFound = (error?: IHttpSerializedFetchError | null, id?: string) => {
|
||||
const { packagePolicyId } = useGetUrlParams();
|
||||
const updateUrlParams = useUrlParams()[1];
|
||||
|
||||
|
|
|
@ -22,17 +22,21 @@ interface Props {
|
|||
export const MonitorDetailsLinkPortal = ({ name, configId, locationId, updateUrl }: Props) => {
|
||||
return (
|
||||
<InPortal node={MonitorDetailsLinkPortalNode}>
|
||||
<MonitorDetailsLink
|
||||
name={name}
|
||||
configId={configId}
|
||||
locationId={locationId}
|
||||
updateUrl={updateUrl}
|
||||
/>
|
||||
{locationId ? (
|
||||
<MonitorDetailsLinkWithLocation
|
||||
name={name}
|
||||
configId={configId}
|
||||
locationId={locationId}
|
||||
updateUrl={updateUrl}
|
||||
/>
|
||||
) : (
|
||||
<MonitorDetailsLink name={name} configId={configId} />
|
||||
)}
|
||||
</InPortal>
|
||||
);
|
||||
};
|
||||
|
||||
export const MonitorDetailsLink = ({ name, configId, locationId, updateUrl }: Props) => {
|
||||
const MonitorDetailsLinkWithLocation = ({ name, configId, locationId, updateUrl }: Props) => {
|
||||
const selectedLocation = useSelectedLocation(updateUrl);
|
||||
|
||||
let locId = locationId;
|
||||
|
@ -45,6 +49,18 @@ export const MonitorDetailsLink = ({ name, configId, locationId, updateUrl }: Pr
|
|||
const href = history.createHref({
|
||||
pathname: locId ? `monitor/${configId}?locationId=${locId}` : `monitor/${configId}`,
|
||||
});
|
||||
return <MonitorLink href={href} name={name} />;
|
||||
};
|
||||
|
||||
const MonitorDetailsLink = ({ name, configId }: Props) => {
|
||||
const history = useHistory();
|
||||
const href = history.createHref({
|
||||
pathname: `monitor/${configId}`,
|
||||
});
|
||||
return <MonitorLink href={href} name={name} />;
|
||||
};
|
||||
|
||||
const MonitorLink = ({ href, name }: { href: string; name: string }) => {
|
||||
return (
|
||||
<EuiLink data-test-subj="syntheticsMonitorDetailsLinkLink" href={href}>
|
||||
<EuiIcon type="arrowLeft" /> {name}
|
||||
|
|
|
@ -115,6 +115,9 @@ describe('MonitorEditPage', () => {
|
|||
locationsLoaded: true,
|
||||
loading: false,
|
||||
},
|
||||
monitorDetails: {
|
||||
syntheticsMonitorLoading: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -173,6 +176,10 @@ describe('MonitorEditPage', () => {
|
|||
locationsLoaded: true,
|
||||
loading: false,
|
||||
},
|
||||
monitorDetails: {
|
||||
syntheticsMonitorLoading: false,
|
||||
syntheticsMonitorError: new Error('test error'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -10,22 +10,27 @@ import { useParams } from 'react-router-dom';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useTrackPageview, useFetcher } from '@kbn/observability-shared-plugin/public';
|
||||
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
|
||||
import { useTrackPageview } from '@kbn/observability-shared-plugin/public';
|
||||
import { CanUsePublicLocationsCallout } from './steps/can_use_public_locations_callout';
|
||||
import { DisabledCallout } from '../monitors_page/management/disabled_callout';
|
||||
import { useCanUsePublicLocations } from '../../../../hooks/use_capabilities';
|
||||
import { EditMonitorNotFound } from './edit_monitor_not_found';
|
||||
import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout';
|
||||
import { ConfigKey, SourceType } from '../../../../../common/runtime_types';
|
||||
import { getServiceLocations, selectServiceLocationsState } from '../../state';
|
||||
import {
|
||||
getMonitorAction,
|
||||
getServiceLocations,
|
||||
selectServiceLocationsState,
|
||||
selectSyntheticsMonitor,
|
||||
selectSyntheticsMonitorError,
|
||||
selectSyntheticsMonitorLoading,
|
||||
} from '../../state';
|
||||
import { AlertingCallout } from '../common/alerting_callout/alerting_callout';
|
||||
import { MonitorSteps } from './steps';
|
||||
import { MonitorForm } from './form';
|
||||
import { LocationsLoadingError } from './locations_loading_error';
|
||||
import { MonitorDetailsLinkPortal } from './monitor_details_portal';
|
||||
import { useMonitorAddEditBreadcrumbs } from './use_breadcrumbs';
|
||||
import { getDecryptedMonitorAPI } from '../../state/monitor_management/api';
|
||||
import { EDIT_MONITOR_STEPS } from './steps/step_config';
|
||||
import { useMonitorNotFound } from './hooks/use_monitor_not_found';
|
||||
|
||||
|
@ -43,17 +48,15 @@ export const MonitorEditPage: React.FC = () => {
|
|||
}
|
||||
}, [locationsLoaded, dispatch]);
|
||||
|
||||
const { data, loading, error } = useFetcher(() => {
|
||||
return getDecryptedMonitorAPI({ id: monitorId });
|
||||
// FIXME: Dario thinks there is a better way to do this but
|
||||
// he's getting tired and maybe the Synthetics folks can fix it
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const data = useSelector(selectSyntheticsMonitor);
|
||||
const isLoading = useSelector(selectSyntheticsMonitorLoading);
|
||||
const error = useSelector(selectSyntheticsMonitorError);
|
||||
|
||||
const monitorNotFoundError = useMonitorNotFound(
|
||||
error as IHttpFetchError<ResponseErrorBody>,
|
||||
data?.id
|
||||
);
|
||||
useEffect(() => {
|
||||
dispatch(getMonitorAction.get({ monitorId }));
|
||||
}, [dispatch, monitorId]);
|
||||
|
||||
const monitorNotFoundError = useMonitorNotFound(error, data?.id);
|
||||
|
||||
const canUsePublicLocations = useCanUsePublicLocations(data?.[ConfigKey.LOCATIONS]);
|
||||
|
||||
|
@ -93,7 +96,7 @@ export const MonitorEditPage: React.FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
return data && locationsLoaded && !loading && !error ? (
|
||||
return data && locationsLoaded && !isLoading && !error ? (
|
||||
<>
|
||||
<DisabledCallout />
|
||||
<CanUsePublicLocationsCallout canUsePublicLocations={canUsePublicLocations} />
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ConfigKey, EncryptedSyntheticsSavedMonitor } from '../../../../../../common/runtime_types';
|
||||
import { ConfigKey } from '../../../../../../common/runtime_types';
|
||||
import { useSyntheticsRefreshContext } from '../../../contexts';
|
||||
import {
|
||||
getMonitorAction,
|
||||
|
@ -40,7 +40,8 @@ export const useSelectedMonitor = (monId?: string) => {
|
|||
monitorId && monitorFromList && monitorFromList[ConfigKey.CONFIG_ID] === monitorId;
|
||||
const isLoadedSyntheticsMonitorValid =
|
||||
monitorId && syntheticsMonitor && syntheticsMonitor[ConfigKey.CONFIG_ID] === monitorId;
|
||||
const availableMonitor: EncryptedSyntheticsSavedMonitor | null = isLoadedSyntheticsMonitorValid
|
||||
|
||||
const availableMonitor = isLoadedSyntheticsMonitorValid
|
||||
? syntheticsMonitor
|
||||
: isMonitorFromListValid
|
||||
? monitorFromList
|
||||
|
|
|
@ -23,11 +23,6 @@ TagsListMock.mockReturnValue(<div>Tags list</div>);
|
|||
|
||||
describe('Monitor Detail Flyout', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({
|
||||
status: observabilitySharedPublic.FETCH_STATUS.PENDING,
|
||||
data: null,
|
||||
refetch: () => null,
|
||||
});
|
||||
jest
|
||||
.spyOn(observabilitySharedPublic, 'useTheme')
|
||||
.mockReturnValue({ eui: { euiColorVis0: 'red', euiColorVis9: 'red' } } as any);
|
||||
|
@ -76,11 +71,6 @@ describe('Monitor Detail Flyout', () => {
|
|||
|
||||
it('renders error boundary for fetch failure', () => {
|
||||
const testErrorText = 'This is a test error';
|
||||
jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({
|
||||
status: observabilitySharedPublic.FETCH_STATUS.FAILURE,
|
||||
error: new Error('This is a test error'),
|
||||
refetch: () => null,
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
<MonitorDetailFlyout
|
||||
|
@ -91,17 +81,19 @@ describe('Monitor Detail Flyout', () => {
|
|||
onClose={jest.fn()}
|
||||
onEnabledChange={jest.fn()}
|
||||
onLocationChange={jest.fn()}
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
state: {
|
||||
monitorDetails: {
|
||||
syntheticsMonitorError: { body: { message: 'This is a test error' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
getByText(testErrorText);
|
||||
});
|
||||
|
||||
it('renders loading state while fetching', () => {
|
||||
jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({
|
||||
status: observabilitySharedPublic.FETCH_STATUS.LOADING,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
const { getByRole } = render(
|
||||
<MonitorDetailFlyout
|
||||
configId="123456"
|
||||
|
@ -111,28 +103,20 @@ describe('Monitor Detail Flyout', () => {
|
|||
onClose={jest.fn()}
|
||||
onEnabledChange={jest.fn()}
|
||||
onLocationChange={jest.fn()}
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
state: {
|
||||
monitorDetails: {
|
||||
syntheticsMonitorLoading: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(getByRole('progressbar'));
|
||||
});
|
||||
|
||||
it('renders details for fetch success', () => {
|
||||
jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({
|
||||
status: observabilitySharedPublic.FETCH_STATUS.SUCCESS,
|
||||
data: {
|
||||
enabled: true,
|
||||
type: 'http',
|
||||
name: 'test-monitor',
|
||||
schedule: {
|
||||
number: '1',
|
||||
unit: 'm',
|
||||
},
|
||||
tags: ['prod'],
|
||||
config_id: 'test-id',
|
||||
},
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
const detailLink = '/app/synthetics/monitor/test-id';
|
||||
jest.spyOn(monitorDetailLocator, 'useMonitorDetailLocator').mockReturnValue(detailLink);
|
||||
jest.spyOn(monitorDetailLocator, 'useMonitorDetailLocator').mockReturnValue(detailLink);
|
||||
|
@ -146,7 +130,24 @@ describe('Monitor Detail Flyout', () => {
|
|||
onClose={jest.fn()}
|
||||
onEnabledChange={jest.fn()}
|
||||
onLocationChange={jest.fn()}
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
state: {
|
||||
monitorDetails: {
|
||||
syntheticsMonitor: {
|
||||
enabled: true,
|
||||
type: 'http',
|
||||
name: 'test-monitor',
|
||||
schedule: {
|
||||
number: '1',
|
||||
unit: 'm',
|
||||
},
|
||||
tags: ['prod'],
|
||||
config_id: 'test-id',
|
||||
} as any,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(getByText('Every 1 minute'));
|
||||
|
|
|
@ -29,7 +29,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTheme, FETCH_STATUS, useFetcher } from '@kbn/observability-shared-plugin/public';
|
||||
import { useTheme } from '@kbn/observability-shared-plugin/public';
|
||||
import { useOverviewStatus } from '../../hooks/use_overview_status';
|
||||
import { MonitorDetailsPanel } from '../../../common/components/monitor_details_panel';
|
||||
import { ClientPluginsStart } from '../../../../../../plugin';
|
||||
|
@ -37,8 +37,12 @@ import { LocationsStatus, useStatusByLocation } from '../../../../hooks/use_stat
|
|||
import { MonitorEnabled } from '../../management/monitor_list_table/monitor_enabled';
|
||||
import { ActionsPopover } from './actions_popover';
|
||||
import {
|
||||
getMonitorAction,
|
||||
selectMonitorUpsertStatus,
|
||||
selectServiceLocationsState,
|
||||
selectSyntheticsMonitor,
|
||||
selectSyntheticsMonitorError,
|
||||
selectSyntheticsMonitorLoading,
|
||||
setFlyoutConfig,
|
||||
} from '../../../../state';
|
||||
import { useMonitorDetail } from '../../../../hooks/use_monitor_detail';
|
||||
|
@ -46,7 +50,6 @@ import { ConfigKey, EncryptedSyntheticsMonitor, OverviewStatusMetaData } from '.
|
|||
import { useMonitorDetailLocator } from '../../../../hooks/use_monitor_detail_locator';
|
||||
import { MonitorStatus } from '../../../common/components/monitor_status';
|
||||
import { MonitorLocationSelect } from '../../../common/components/monitor_location_select';
|
||||
import { fetchSyntheticsMonitor } from '../../../../state/monitor_details/api';
|
||||
|
||||
interface Props {
|
||||
configId: string;
|
||||
|
@ -250,21 +253,15 @@ export function MonitorDetailFlyout(props: Props) {
|
|||
}, [dispatch]);
|
||||
|
||||
const upsertStatus = useSelector(selectMonitorUpsertStatus(configId));
|
||||
const monitorObject = useSelector(selectSyntheticsMonitor);
|
||||
const isLoading = useSelector(selectSyntheticsMonitorLoading);
|
||||
const error = useSelector(selectSyntheticsMonitorError);
|
||||
|
||||
const upsertSuccess = upsertStatus?.status === 'success';
|
||||
|
||||
const {
|
||||
data: monitorObject,
|
||||
error,
|
||||
status,
|
||||
loading,
|
||||
} = useFetcher(
|
||||
() => fetchSyntheticsMonitor({ monitorId: configId }),
|
||||
// FIXME: Dario thinks there is a better way to do this but
|
||||
// he's getting tired and maybe the Synthetics folks can fix it
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[configId, upsertSuccess]
|
||||
);
|
||||
useEffect(() => {
|
||||
dispatch(getMonitorAction.get({ monitorId: configId }));
|
||||
}, [configId, dispatch, upsertSuccess]);
|
||||
|
||||
const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false);
|
||||
|
||||
|
@ -283,9 +280,9 @@ export function MonitorDetailFlyout(props: Props) {
|
|||
onClose={props.onClose}
|
||||
paddingSize="none"
|
||||
>
|
||||
{status === FETCH_STATUS.FAILURE && <EuiErrorBoundary>{error?.message}</EuiErrorBoundary>}
|
||||
{status === FETCH_STATUS.LOADING && <LoadingState />}
|
||||
{status === FETCH_STATUS.SUCCESS && monitorObject && (
|
||||
{error && !isLoading && <EuiErrorBoundary>{error?.body?.message}</EuiErrorBoundary>}
|
||||
{isLoading && <LoadingState />}
|
||||
{monitorObject && (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
|
||||
|
@ -332,7 +329,7 @@ export function MonitorDetailFlyout(props: Props) {
|
|||
...monitorObject,
|
||||
id,
|
||||
}}
|
||||
loading={Boolean(loading)}
|
||||
loading={Boolean(isLoading)}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -7,21 +7,16 @@
|
|||
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { MostRecentPingsRequest } from './api';
|
||||
import {
|
||||
Ping,
|
||||
PingsResponse,
|
||||
EncryptedSyntheticsSavedMonitor,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { Ping, PingsResponse, SyntheticsMonitorWithId } from '../../../../../common/runtime_types';
|
||||
import { createAsyncAction } from '../utils/actions';
|
||||
|
||||
export const setMonitorDetailsLocationAction = createAction<string>(
|
||||
'[MONITOR SUMMARY] SET LOCATION'
|
||||
);
|
||||
|
||||
export const getMonitorAction = createAsyncAction<
|
||||
{ monitorId: string },
|
||||
EncryptedSyntheticsSavedMonitor
|
||||
>('[MONITOR DETAILS] GET MONITOR');
|
||||
export const getMonitorAction = createAsyncAction<{ monitorId: string }, SyntheticsMonitorWithId>(
|
||||
'[MONITOR DETAILS] GET MONITOR'
|
||||
);
|
||||
|
||||
export const getMonitorLastRunAction = createAsyncAction<
|
||||
{ monitorId: string; locationId: string },
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
import moment from 'moment';
|
||||
import { apiService } from '../../../../utils/api_service';
|
||||
import {
|
||||
EncryptedSyntheticsSavedMonitor,
|
||||
EncryptedSyntheticsMonitorCodec,
|
||||
PingsResponse,
|
||||
PingsResponseType,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
||||
|
||||
|
@ -67,11 +67,13 @@ export const fetchSyntheticsMonitor = async ({
|
|||
monitorId,
|
||||
}: {
|
||||
monitorId: string;
|
||||
}): Promise<EncryptedSyntheticsSavedMonitor> =>
|
||||
apiService.get<EncryptedSyntheticsSavedMonitor>(
|
||||
}): Promise<SyntheticsMonitorWithId> => {
|
||||
return apiService.get<SyntheticsMonitorWithId>(
|
||||
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId),
|
||||
{
|
||||
internal: true,
|
||||
version: INITIAL_REST_VERSION,
|
||||
},
|
||||
EncryptedSyntheticsMonitorCodec
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../../common/runtime_types';
|
||||
import { Ping, SyntheticsMonitorWithId } from '../../../../../common/runtime_types';
|
||||
import { checkIsStalePing } from '../../utils/monitor_test_result/check_pings';
|
||||
import { enableMonitorAlertAction } from '../monitor_list/actions';
|
||||
|
||||
|
@ -34,7 +34,8 @@ export interface MonitorDetailsState {
|
|||
loaded: boolean;
|
||||
};
|
||||
syntheticsMonitorLoading: boolean;
|
||||
syntheticsMonitor: EncryptedSyntheticsSavedMonitor | null;
|
||||
syntheticsMonitor: SyntheticsMonitorWithId | null;
|
||||
syntheticsMonitorError?: IHttpSerializedFetchError | null;
|
||||
syntheticsMonitorDispatchedAt: number;
|
||||
error: IHttpSerializedFetchError | null;
|
||||
selectedLocationId: string | null;
|
||||
|
@ -97,15 +98,15 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => {
|
|||
.addCase(getMonitorAction.get, (state, action) => {
|
||||
state.syntheticsMonitorDispatchedAt = action.meta.dispatchedAt;
|
||||
state.syntheticsMonitorLoading = true;
|
||||
state.error = null;
|
||||
state.syntheticsMonitorError = null;
|
||||
})
|
||||
.addCase(getMonitorAction.success, (state, action) => {
|
||||
state.syntheticsMonitor = action.payload;
|
||||
state.syntheticsMonitorLoading = false;
|
||||
state.error = null;
|
||||
state.syntheticsMonitorError = null;
|
||||
})
|
||||
.addCase(getMonitorAction.fail, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.syntheticsMonitorError = action.payload;
|
||||
state.syntheticsMonitorLoading = false;
|
||||
})
|
||||
.addCase(enableMonitorAlertAction.success, (state, action) => {
|
||||
|
|
|
@ -27,3 +27,14 @@ export const selectMonitorPingsMetadata = createSelector(getState, (state) => st
|
|||
export const selectPingsError = createSelector(getState, (state) => state.error);
|
||||
|
||||
export const selectStatusFilter = createSelector(getState, (state) => state.statusFilter);
|
||||
|
||||
export const selectSyntheticsMonitor = createSelector(getState, (state) => state.syntheticsMonitor);
|
||||
export const selectSyntheticsMonitorError = createSelector(
|
||||
getState,
|
||||
(state) => state.syntheticsMonitorError
|
||||
);
|
||||
|
||||
export const selectSyntheticsMonitorLoading = createSelector(
|
||||
getState,
|
||||
(state) => state.syntheticsMonitorLoading
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
MonitorManagementListResult,
|
||||
MonitorFiltersResult,
|
||||
EncryptedSyntheticsSavedMonitor,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { createAsyncAction } from '../utils/actions';
|
||||
|
||||
|
@ -34,7 +35,7 @@ export const fetchUpsertFailureAction = createAction<UpsertMonitorError>(
|
|||
|
||||
export const enableMonitorAlertAction = createAsyncAction<
|
||||
UpsertMonitorRequest,
|
||||
EncryptedSyntheticsSavedMonitor,
|
||||
SyntheticsMonitorWithId,
|
||||
UpsertMonitorError
|
||||
>('enableMonitorAlertAction');
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ export const fetchUpsertMonitor = async ({
|
|||
null,
|
||||
{
|
||||
version: INITIAL_REST_VERSION,
|
||||
ui: true,
|
||||
internal: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -9,7 +9,11 @@ import { PayloadAction } from '@reduxjs/toolkit';
|
|||
import { call, put, takeEvery, select, takeLatest, debounce } from 'redux-saga/effects';
|
||||
import { quietFetchOverviewStatusAction } from '../overview_status';
|
||||
import { enableDefaultAlertingAction } from '../alert_rules';
|
||||
import { ConfigKey, EncryptedSyntheticsSavedMonitor } from '../../../../../common/runtime_types';
|
||||
import {
|
||||
ConfigKey,
|
||||
EncryptedSyntheticsSavedMonitor,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { kibanaService } from '../../../../utils/kibana_service';
|
||||
import { MonitorOverviewPageState } from '../overview';
|
||||
import { selectOverviewState } from '../overview/selectors';
|
||||
|
@ -48,7 +52,7 @@ export function* enableMonitorAlertEffect() {
|
|||
function* (action: PayloadAction<UpsertMonitorRequest>): Generator {
|
||||
try {
|
||||
const response = yield call(fetchUpsertMonitor, action.payload);
|
||||
yield put(enableMonitorAlertAction.success(response as EncryptedSyntheticsSavedMonitor));
|
||||
yield put(enableMonitorAlertAction.success(response as SyntheticsMonitorWithId));
|
||||
sendSuccessToast(action.payload.success);
|
||||
if (
|
||||
(response as EncryptedSyntheticsSavedMonitor)[ConfigKey.ALERT_CONFIG]?.status?.enabled
|
||||
|
|
|
@ -11,12 +11,12 @@ import { apiService } from '../../../../utils/api_service';
|
|||
import {
|
||||
EncryptedSyntheticsMonitor,
|
||||
SyntheticsMonitor,
|
||||
SyntheticsMonitorCodec,
|
||||
ServiceLocationErrorsResponse,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
||||
|
||||
export type UpsertMonitorResponse = ServiceLocationErrorsResponse | EncryptedSyntheticsMonitor;
|
||||
export type UpsertMonitorResponse = ServiceLocationErrorsResponse | SyntheticsMonitorWithId;
|
||||
|
||||
export const createMonitorAPI = async ({
|
||||
monitor,
|
||||
|
@ -25,6 +25,7 @@ export const createMonitorAPI = async ({
|
|||
}): Promise<UpsertMonitorResponse> => {
|
||||
return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, monitor, null, {
|
||||
version: INITIAL_REST_VERSION,
|
||||
internal: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -53,21 +54,11 @@ export const updateMonitorAPI = async ({
|
|||
id: string;
|
||||
}): Promise<UpsertMonitorResponse> => {
|
||||
return await apiService.put(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor, null, {
|
||||
ui: true,
|
||||
internal: true,
|
||||
version: INITIAL_REST_VERSION,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDecryptedMonitorAPI = async ({ id }: { id: string }): Promise<SyntheticsMonitor> =>
|
||||
apiService.get(
|
||||
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', id),
|
||||
{
|
||||
decrypted: true,
|
||||
version: INITIAL_REST_VERSION,
|
||||
},
|
||||
SyntheticsMonitorCodec
|
||||
);
|
||||
|
||||
export const fetchProjectAPIKey = async (
|
||||
accessToElasticManagedLocations: boolean
|
||||
): Promise<ProjectAPIKeyResponse> => {
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { SavedObject } from '@kbn/core/server';
|
||||
import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import { syntheticsMonitorType } from '../../common/types/saved_objects';
|
||||
import {
|
||||
SyntheticsMonitorWithSecretsAttributes,
|
||||
EncryptedSyntheticsMonitorAttributes,
|
||||
SyntheticsMonitor,
|
||||
} from '../../common/runtime_types';
|
||||
import { normalizeSecrets } from '../synthetics_service/utils/secrets';
|
||||
|
@ -18,28 +17,22 @@ import { normalizeSecrets } from '../synthetics_service/utils/secrets';
|
|||
export const getSyntheticsMonitor = async ({
|
||||
monitorId,
|
||||
encryptedSavedObjectsClient,
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
}: {
|
||||
monitorId: string;
|
||||
spaceId: string;
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}): Promise<SyntheticsMonitor> => {
|
||||
}): Promise<SavedObject<SyntheticsMonitor>> => {
|
||||
try {
|
||||
const encryptedMonitor = await savedObjectsClient.get<EncryptedSyntheticsMonitorAttributes>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
);
|
||||
|
||||
const decryptedMonitor =
|
||||
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecretsAttributes>(
|
||||
syntheticsMonitorType,
|
||||
monitorId,
|
||||
{
|
||||
namespace: encryptedMonitor.namespaces?.[0],
|
||||
namespace: spaceId,
|
||||
}
|
||||
);
|
||||
const { attributes } = normalizeSecrets(decryptedMonitor);
|
||||
return attributes;
|
||||
return normalizeSecrets(decryptedMonitor);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { AddEditMonitorAPI, CreateMonitorPayLoad } from './add_monitor/add_monit
|
|||
import { SyntheticsRestApiRouteFactory } from '../types';
|
||||
import { SYNTHETICS_API_URLS } from '../../../common/constants';
|
||||
import { normalizeAPIConfig, validateMonitor } from './monitor_validation';
|
||||
import { mapSavedObjectToMonitor } from './helper';
|
||||
import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor';
|
||||
|
||||
export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
||||
method: 'POST',
|
||||
|
@ -26,13 +26,18 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
|||
id: schema.maybe(schema.string()),
|
||||
preserve_namespace: schema.maybe(schema.boolean()),
|
||||
gettingStarted: schema.maybe(schema.boolean()),
|
||||
internal: schema.maybe(
|
||||
schema.boolean({
|
||||
defaultValue: false,
|
||||
})
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (routeContext): Promise<any> => {
|
||||
const { request, response, server } = routeContext;
|
||||
// usually id is auto generated, but this is useful for testing
|
||||
const { id } = request.query;
|
||||
const { id, internal } = request.query;
|
||||
|
||||
const addMonitorAPI = new AddEditMonitorAPI(routeContext);
|
||||
|
||||
|
@ -113,7 +118,7 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
|||
addMonitorAPI.initDefaultAlerts(newMonitor.attributes.name);
|
||||
addMonitorAPI.setupGettingStarted(newMonitor.id);
|
||||
|
||||
return mapSavedObjectToMonitor(newMonitor);
|
||||
return mapSavedObjectToMonitor({ monitor: newMonitor, internal });
|
||||
} catch (getErr) {
|
||||
server.logger.error(getErr);
|
||||
if (getErr instanceof InvalidLocationError) {
|
||||
|
|
|
@ -188,7 +188,7 @@ export class AddEditMonitorAPI {
|
|||
prevLocations?: MonitorFields['locations']
|
||||
) {
|
||||
const { savedObjectsClient, syntheticsMonitorClient, request } = this.routeContext;
|
||||
const ui = Boolean((request.query as { ui?: boolean })?.ui);
|
||||
const internal = Boolean((request.query as { internal?: boolean })?.internal);
|
||||
const {
|
||||
locations,
|
||||
private_locations: privateLocations,
|
||||
|
@ -212,7 +212,7 @@ export class AddEditMonitorAPI {
|
|||
if (!locations && !privateLocations && prevLocations) {
|
||||
locationsVal = prevLocations;
|
||||
} else {
|
||||
const monitorLocations = parseMonitorLocations(monitorPayload, prevLocations, ui);
|
||||
const monitorLocations = parseMonitorLocations(monitorPayload, prevLocations, internal);
|
||||
|
||||
if (monitorLocations.privateLocations.length > 0) {
|
||||
this.allPrivateLocations = await getPrivateLocations(savedObjectsClient);
|
||||
|
|
|
@ -25,7 +25,7 @@ export const getPrivateLocationsForMonitor = async (
|
|||
export const parseMonitorLocations = (
|
||||
monitorPayload: CreateMonitorPayLoad,
|
||||
prevLocations?: MonitorFields['locations'],
|
||||
ui: boolean = false
|
||||
internal: boolean = false
|
||||
) => {
|
||||
const { locations, private_locations: privateLocations } = monitorPayload;
|
||||
|
||||
|
@ -37,11 +37,11 @@ export const parseMonitorLocations = (
|
|||
let pvtLocs = [...(privateLocations ?? []), ...extractPvtLocs]?.map((loc) =>
|
||||
typeof loc === 'string' ? loc : loc.id
|
||||
);
|
||||
if (ui && !privateLocations && !locations && prevLocations) {
|
||||
if (internal && !privateLocations && !locations && prevLocations) {
|
||||
locs = prevLocations.filter((loc) => loc.isServiceManaged).map((loc) => loc.id);
|
||||
pvtLocs = prevLocations.filter((loc) => !loc.isServiceManaged).map((loc) => loc.id);
|
||||
} else {
|
||||
if (prevLocations && !ui) {
|
||||
if (prevLocations && !internal) {
|
||||
if (!locations && !privateLocations) {
|
||||
locs = prevLocations.filter((loc) => loc.isServiceManaged).map((loc) => loc.id);
|
||||
pvtLocs = prevLocations.filter((loc) => !loc.isServiceManaged).map((loc) => loc.id);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { AddEditMonitorAPI, CreateMonitorPayLoad } from './add_monitor/add_monit
|
|||
import { ELASTIC_MANAGED_LOCATIONS_DISABLED } from './add_monitor_project';
|
||||
import { getDecryptedMonitor } from '../../saved_objects/synthetics_monitor';
|
||||
import { getPrivateLocations } from '../../synthetics_service/get_private_locations';
|
||||
import { mergeSourceMonitor } from './helper';
|
||||
import { mergeSourceMonitor } from './formatters/saved_object_to_monitor';
|
||||
import { RouteContext, SyntheticsRestApiRouteFactory } from '../types';
|
||||
import { syntheticsMonitorType } from '../../../common/types/saved_objects';
|
||||
import {
|
||||
|
@ -33,7 +33,7 @@ import {
|
|||
formatTelemetryUpdateEvent,
|
||||
} from '../telemetry/monitor_upgrade_sender';
|
||||
import { formatSecrets, normalizeSecrets } from '../../synthetics_service/utils/secrets';
|
||||
import { mapSavedObjectToMonitor } from './helper';
|
||||
import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor';
|
||||
|
||||
// Simplify return promise type and type it with runtime_types
|
||||
export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
||||
|
@ -46,7 +46,11 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
|
|||
monitorId: schema.string(),
|
||||
}),
|
||||
query: schema.object({
|
||||
ui: schema.maybe(schema.boolean()),
|
||||
internal: schema.maybe(
|
||||
schema.boolean({
|
||||
defaultValue: false,
|
||||
})
|
||||
),
|
||||
}),
|
||||
body: schema.any(),
|
||||
},
|
||||
|
@ -55,7 +59,7 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
|
|||
const { request, response, spaceId, server } = routeContext;
|
||||
const { logger } = server;
|
||||
const monitor = request.body as SyntheticsMonitor;
|
||||
const reqQuery = request.query as { ui?: boolean };
|
||||
const reqQuery = request.query as { internal?: boolean };
|
||||
const { monitorId } = request.params;
|
||||
|
||||
if (!monitor || typeof monitor !== 'object' || isEmpty(monitor) || Array.isArray(monitor)) {
|
||||
|
@ -86,7 +90,7 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
|
|||
const previousMonitor = await getDecryptedMonitor(server, monitorId, spaceId);
|
||||
const normalizedPreviousMonitor = normalizeSecrets(previousMonitor).attributes;
|
||||
|
||||
if (normalizedPreviousMonitor.origin !== 'ui' && !reqQuery.ui) {
|
||||
if (normalizedPreviousMonitor.origin !== 'ui' && !reqQuery.internal) {
|
||||
return response.badRequest(getInvalidOriginError(monitor));
|
||||
}
|
||||
|
||||
|
@ -170,9 +174,13 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
|
|||
});
|
||||
}
|
||||
|
||||
return mapSavedObjectToMonitor(
|
||||
editedMonitorSavedObject as SavedObject<EncryptedSyntheticsMonitorAttributes>
|
||||
);
|
||||
return mapSavedObjectToMonitor({
|
||||
internal: reqQuery.internal,
|
||||
monitor: {
|
||||
...(editedMonitorSavedObject as SavedObject<EncryptedSyntheticsMonitorAttributes>),
|
||||
created_at: previousMonitor.created_at,
|
||||
},
|
||||
});
|
||||
} catch (updateErr) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(updateErr)) {
|
||||
return getMonitorNotFoundResponse(response, monitorId);
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mapSavedObjectToMonitor, mergeSourceMonitor } from './helper';
|
||||
import { EncryptedSyntheticsMonitor } from '../../../common/runtime_types';
|
||||
import { mapSavedObjectToMonitor, mergeSourceMonitor } from './saved_object_to_monitor';
|
||||
import { EncryptedSyntheticsMonitor } from '../../../../common/runtime_types';
|
||||
|
||||
describe('mergeSourceMonitor', () => {
|
||||
it('should merge keys', function () {
|
||||
|
@ -66,8 +66,82 @@ describe('mergeSourceMonitor', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should omit null or undefined values', () => {
|
||||
const result = mapSavedObjectToMonitor({ attributes: testMonitor } as any);
|
||||
it('should not omit null or undefined values', () => {
|
||||
const result = mapSavedObjectToMonitor({ monitor: { attributes: testMonitor } } as any);
|
||||
|
||||
expect(result).toEqual({
|
||||
alert: {
|
||||
status: {
|
||||
enabled: true,
|
||||
},
|
||||
tls: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
config_id: 'ae88f0aa-9c7d-4a5f-96dc-89d65a0ca947',
|
||||
custom_heartbeat_id: 'todos-lightweight-test-projects-default',
|
||||
enabled: true,
|
||||
id: 'todos-lightweight-test-projects-default',
|
||||
ipv4: true,
|
||||
ipv6: true,
|
||||
locations: [
|
||||
{
|
||||
geo: {
|
||||
lat: 41.25,
|
||||
lon: -95.86,
|
||||
},
|
||||
id: 'us_central',
|
||||
isServiceManaged: true,
|
||||
label: 'North America - US Central',
|
||||
},
|
||||
],
|
||||
max_redirects: 0,
|
||||
mode: 'any',
|
||||
name: 'Todos Lightweight',
|
||||
namespace: 'default',
|
||||
original_space: 'default',
|
||||
proxy_url: '',
|
||||
retest_on_failure: true,
|
||||
revision: 21,
|
||||
schedule: {
|
||||
number: '3',
|
||||
unit: 'm',
|
||||
},
|
||||
'service.name': '',
|
||||
tags: [],
|
||||
timeout: '16',
|
||||
type: 'http',
|
||||
url: '${devUrl}',
|
||||
'url.port': null,
|
||||
ssl: {
|
||||
certificate: '',
|
||||
certificate_authorities: '',
|
||||
supported_protocols: ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
|
||||
verification_mode: 'full',
|
||||
key: 'test-key',
|
||||
},
|
||||
response: {
|
||||
include_body: 'on_error',
|
||||
include_body_max_bytes: '1024',
|
||||
include_headers: true,
|
||||
},
|
||||
check: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
},
|
||||
response: {
|
||||
status: ['404'],
|
||||
},
|
||||
},
|
||||
params: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not omit null or undefined values with ui', () => {
|
||||
const result = mapSavedObjectToMonitor({
|
||||
monitor: { attributes: { ...testMonitor } },
|
||||
internal: true,
|
||||
} as any);
|
||||
|
||||
expect(result).toEqual({
|
||||
__ui: {
|
||||
|
@ -100,27 +174,37 @@ describe('mergeSourceMonitor', () => {
|
|||
label: 'North America - US Central',
|
||||
},
|
||||
],
|
||||
max_attempts: 2,
|
||||
max_redirects: '0',
|
||||
mode: 'any',
|
||||
name: 'Todos Lightweight',
|
||||
namespace: 'default',
|
||||
origin: 'project',
|
||||
original_space: 'default',
|
||||
project_id: 'test-projects',
|
||||
'response.include_body': 'on_error',
|
||||
'response.include_body_max_bytes': '1024',
|
||||
'response.include_headers': true,
|
||||
proxy_url: '',
|
||||
revision: 21,
|
||||
schedule: {
|
||||
number: '3',
|
||||
unit: 'm',
|
||||
},
|
||||
'service.name': '',
|
||||
'ssl.certificate': '',
|
||||
'ssl.certificate_authorities': '',
|
||||
'ssl.key': 'test-key',
|
||||
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
|
||||
'ssl.verification_mode': 'full',
|
||||
'response.include_body': 'on_error',
|
||||
'response.include_body_max_bytes': '1024',
|
||||
'response.include_headers': true,
|
||||
tags: [],
|
||||
timeout: '16',
|
||||
type: 'http',
|
||||
url: '${devUrl}',
|
||||
'url.port': null,
|
||||
form_monitor_type: 'http',
|
||||
hash: 'f4b6u3Q/PMK5KzEtPeMNzXJBA46rt+yilohaAoqMzqk=',
|
||||
journey_id: 'todos-lightweight',
|
||||
max_attempts: 2,
|
||||
origin: 'project',
|
||||
urls: '${devUrl}',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -179,6 +263,7 @@ const testMonitor = {
|
|||
ipv4: true,
|
||||
ipv6: true,
|
||||
'ssl.certificate_authorities': '',
|
||||
'ssl.key': 'test-key',
|
||||
'ssl.certificate': '',
|
||||
'ssl.verification_mode': 'full',
|
||||
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* 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 { SavedObject } from '@kbn/core/server';
|
||||
import { mergeWith, omit, omitBy } from 'lodash';
|
||||
import {
|
||||
ConfigKey,
|
||||
EncryptedSyntheticsMonitor,
|
||||
MonitorFields,
|
||||
MonitorFieldsResult,
|
||||
} from '../../../../common/runtime_types';
|
||||
|
||||
const keysToOmit = [
|
||||
ConfigKey.URLS,
|
||||
ConfigKey.SOURCE_INLINE,
|
||||
ConfigKey.HOSTS,
|
||||
ConfigKey.CONFIG_HASH,
|
||||
ConfigKey.JOURNEY_ID,
|
||||
ConfigKey.FORM_MONITOR_TYPE,
|
||||
ConfigKey.MAX_ATTEMPTS,
|
||||
ConfigKey.MONITOR_SOURCE_TYPE,
|
||||
ConfigKey.METADATA,
|
||||
ConfigKey.SOURCE_PROJECT_CONTENT,
|
||||
ConfigKey.PROJECT_ID,
|
||||
ConfigKey.JOURNEY_FILTERS_MATCH,
|
||||
ConfigKey.JOURNEY_FILTERS_TAGS,
|
||||
ConfigKey.MONITOR_SOURCE_TYPE,
|
||||
];
|
||||
|
||||
type Result = MonitorFieldsResult & {
|
||||
url?: string;
|
||||
host?: string;
|
||||
inline_script?: string;
|
||||
ssl: Record<string, any>;
|
||||
response: Record<string, any>;
|
||||
check: Record<string, any>;
|
||||
};
|
||||
|
||||
export const transformPublicKeys = (result: Result) => {
|
||||
let formattedResult = {
|
||||
...result,
|
||||
[ConfigKey.PARAMS]: formatParams(result),
|
||||
retest_on_failure: (result[ConfigKey.MAX_ATTEMPTS] ?? 1) > 1,
|
||||
...(result[ConfigKey.HOSTS] && { host: result[ConfigKey.HOSTS] }),
|
||||
...(result[ConfigKey.URLS] && { url: result[ConfigKey.URLS] }),
|
||||
};
|
||||
if (formattedResult[ConfigKey.MONITOR_TYPE] === 'browser') {
|
||||
formattedResult = {
|
||||
...formattedResult,
|
||||
...(result[ConfigKey.SOURCE_INLINE] && { inline_script: result[ConfigKey.SOURCE_INLINE] }),
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: formatPWOptions(result),
|
||||
};
|
||||
} else {
|
||||
formattedResult.ssl = formatNestedFields(formattedResult, 'ssl');
|
||||
formattedResult.response = formatNestedFields(formattedResult, 'response');
|
||||
formattedResult.check = formatNestedFields(formattedResult, 'check');
|
||||
if (formattedResult[ConfigKey.MAX_REDIRECTS]) {
|
||||
formattedResult[ConfigKey.MAX_REDIRECTS] = Number(formattedResult[ConfigKey.MAX_REDIRECTS]);
|
||||
}
|
||||
}
|
||||
const res = omit(formattedResult, keysToOmit) as Result;
|
||||
|
||||
return omitBy(
|
||||
res,
|
||||
(value, key) =>
|
||||
key.startsWith('response.') || key.startsWith('ssl.') || key.startsWith('check.')
|
||||
);
|
||||
};
|
||||
|
||||
export function mapSavedObjectToMonitor({
|
||||
monitor,
|
||||
internal = false,
|
||||
}: {
|
||||
monitor: SavedObject<MonitorFields | EncryptedSyntheticsMonitor>;
|
||||
internal?: boolean;
|
||||
}) {
|
||||
const result = {
|
||||
...monitor.attributes,
|
||||
created_at: monitor.created_at,
|
||||
updated_at: monitor.updated_at,
|
||||
} as Result;
|
||||
if (internal) {
|
||||
return result;
|
||||
}
|
||||
return transformPublicKeys(result);
|
||||
}
|
||||
export function mergeSourceMonitor(
|
||||
normalizedPreviousMonitor: EncryptedSyntheticsMonitor,
|
||||
monitor: EncryptedSyntheticsMonitor
|
||||
) {
|
||||
return mergeWith({ ...normalizedPreviousMonitor }, monitor, customizer);
|
||||
}
|
||||
|
||||
// Ensure that METADATA is merged deeply, to protect AAD and prevent decryption errors
|
||||
const customizer = (destVal: any, srcValue: any, key: string) => {
|
||||
if (key === ConfigKey.ALERT_CONFIG) {
|
||||
return { ...destVal, ...srcValue };
|
||||
}
|
||||
if (key !== ConfigKey.METADATA) {
|
||||
return srcValue;
|
||||
}
|
||||
};
|
||||
|
||||
const formatParams = (config: MonitorFields) => {
|
||||
if (config[ConfigKey.PARAMS]) {
|
||||
try {
|
||||
return (config[ConfigKey.PARAMS] = JSON.parse(config[ConfigKey.PARAMS] ?? '{}'));
|
||||
} catch (e) {
|
||||
// ignore
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const formatPWOptions = (config: MonitorFields) => {
|
||||
if (config[ConfigKey.PLAYWRIGHT_OPTIONS]) {
|
||||
try {
|
||||
return (config[ConfigKey.PLAYWRIGHT_OPTIONS] = JSON.parse(
|
||||
config[ConfigKey.PLAYWRIGHT_OPTIONS] ?? '{}'
|
||||
));
|
||||
} catch (e) {
|
||||
// ignore
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// combine same nested fields into same object
|
||||
const formatNestedFields = (
|
||||
config: MonitorFields | Record<string, any>,
|
||||
nestedKey: 'ssl' | 'response' | 'check' | 'request'
|
||||
): Record<string, any> => {
|
||||
const nestedFields = Object.keys(config).filter((key) =>
|
||||
key.startsWith(`${nestedKey}.`)
|
||||
) as ConfigKey[];
|
||||
const obj: Record<string, any> = {};
|
||||
|
||||
nestedFields.forEach((key) => {
|
||||
const newKey = key.replace(`${nestedKey}.`, '');
|
||||
obj[newKey] = config[key];
|
||||
delete config[key];
|
||||
});
|
||||
|
||||
if (nestedKey === 'check') {
|
||||
return {
|
||||
request: formatNestedFields(obj, 'request'),
|
||||
response: formatNestedFields(obj, 'response'),
|
||||
};
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
|
@ -12,7 +12,7 @@ import { isStatusEnabled } from '../../../common/runtime_types/monitor_managemen
|
|||
import { ConfigKey, EncryptedSyntheticsMonitorAttributes } from '../../../common/runtime_types';
|
||||
import { SYNTHETICS_API_URLS } from '../../../common/constants';
|
||||
import { getMonitorNotFoundResponse } from '../synthetics_service/service_errors';
|
||||
import { mapSavedObjectToMonitor } from './helper';
|
||||
import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor';
|
||||
import { getSyntheticsMonitor } from '../../queries/get_monitor';
|
||||
|
||||
export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
||||
|
@ -25,7 +25,11 @@ export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
|||
monitorId: schema.string({ minLength: 1, maxLength: 1024 }),
|
||||
}),
|
||||
query: schema.object({
|
||||
decrypted: schema.maybe(schema.boolean()),
|
||||
internal: schema.maybe(
|
||||
schema.boolean({
|
||||
defaultValue: false,
|
||||
})
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
@ -34,36 +38,36 @@ export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
|||
response,
|
||||
server: { encryptedSavedObjects, coreStart },
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
}): Promise<any> => {
|
||||
const { monitorId } = request.params;
|
||||
try {
|
||||
const { decrypted } = request.query;
|
||||
const { internal } = request.query;
|
||||
|
||||
if (!decrypted) {
|
||||
return mapSavedObjectToMonitor(
|
||||
await savedObjectsClient.get<EncryptedSyntheticsMonitorAttributes>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const canSave =
|
||||
(
|
||||
await coreStart?.capabilities.resolveCapabilities(request, {
|
||||
capabilityPath: 'uptime.*',
|
||||
})
|
||||
).uptime.save ?? false;
|
||||
|
||||
if (Boolean(canSave)) {
|
||||
// only user with write permissions can decrypt the monitor
|
||||
const canSave =
|
||||
(
|
||||
await coreStart?.capabilities.resolveCapabilities(request, {
|
||||
capabilityPath: 'uptime.*',
|
||||
})
|
||||
).uptime.save ?? false;
|
||||
if (!canSave) {
|
||||
return response.forbidden();
|
||||
}
|
||||
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjects.getClient();
|
||||
|
||||
return await getSyntheticsMonitor({
|
||||
const monitor = await getSyntheticsMonitor({
|
||||
monitorId,
|
||||
encryptedSavedObjectsClient,
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
});
|
||||
return mapSavedObjectToMonitor({ monitor, internal });
|
||||
} else {
|
||||
return mapSavedObjectToMonitor({
|
||||
monitor: await savedObjectsClient.get<EncryptedSyntheticsMonitorAttributes>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
),
|
||||
internal,
|
||||
});
|
||||
}
|
||||
} catch (getErr) {
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor';
|
||||
import { SyntheticsRestApiRouteFactory } from '../types';
|
||||
import { SYNTHETICS_API_URLS } from '../../../common/constants';
|
||||
import { getMonitors, isMonitorsQueryFiltered, QuerySchema } from '../common';
|
||||
import { syntheticsMonitorType } from '../../../common/types/saved_objects';
|
||||
import { mapSavedObjectToMonitor } from './helper';
|
||||
|
||||
export const getAllSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
|
||||
method: 'GET',
|
||||
|
@ -39,7 +39,11 @@ export const getAllSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () =>
|
|||
|
||||
return {
|
||||
...rest,
|
||||
monitors: savedObjects.map(mapSavedObjectToMonitor),
|
||||
monitors: savedObjects.map((monitor) =>
|
||||
mapSavedObjectToMonitor({
|
||||
monitor,
|
||||
})
|
||||
),
|
||||
absoluteTotal,
|
||||
perPage: perPageT,
|
||||
syncErrors: syntheticsMonitorClient.syntheticsService.syncErrors,
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* 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 { SavedObject } from '@kbn/core/server';
|
||||
import { mergeWith, omit, omitBy } from 'lodash';
|
||||
import {
|
||||
ConfigKey,
|
||||
EncryptedSyntheticsMonitor,
|
||||
MonitorFields,
|
||||
} from '../../../common/runtime_types';
|
||||
|
||||
const keysToOmit = [
|
||||
ConfigKey.URLS,
|
||||
ConfigKey.SOURCE_INLINE,
|
||||
ConfigKey.HOSTS,
|
||||
ConfigKey.CONFIG_HASH,
|
||||
ConfigKey.JOURNEY_ID,
|
||||
ConfigKey.FORM_MONITOR_TYPE,
|
||||
];
|
||||
|
||||
type Result = MonitorFields & { url?: string; host?: string; inline_script?: string };
|
||||
|
||||
export const transformPublicKeys = (result: Result) => {
|
||||
if (result[ConfigKey.URLS]) {
|
||||
result.url = result[ConfigKey.URLS];
|
||||
}
|
||||
if (result[ConfigKey.SOURCE_INLINE]) {
|
||||
result.inline_script = result[ConfigKey.SOURCE_INLINE];
|
||||
}
|
||||
if (result[ConfigKey.HOSTS]) {
|
||||
result.host = result[ConfigKey.HOSTS];
|
||||
}
|
||||
if (result[ConfigKey.PARAMS]) {
|
||||
try {
|
||||
result[ConfigKey.PARAMS] = JSON.parse(result[ConfigKey.PARAMS] ?? '{}');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (result[ConfigKey.PLAYWRIGHT_OPTIONS]) {
|
||||
try {
|
||||
result[ConfigKey.PLAYWRIGHT_OPTIONS] = JSON.parse(
|
||||
result[ConfigKey.PLAYWRIGHT_OPTIONS] ?? '{}'
|
||||
);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return omit(result, keysToOmit) as Result;
|
||||
};
|
||||
|
||||
export function mapSavedObjectToMonitor(
|
||||
so: SavedObject<MonitorFields | EncryptedSyntheticsMonitor>
|
||||
) {
|
||||
let result = Object.assign(so.attributes, {
|
||||
created_at: so.created_at,
|
||||
updated_at: so.updated_at,
|
||||
}) as Result;
|
||||
result = transformPublicKeys(result);
|
||||
|
||||
// omit undefined value or null value
|
||||
return omitBy(result, removeMonitorEmptyValues);
|
||||
}
|
||||
export function mergeSourceMonitor(
|
||||
normalizedPreviousMonitor: EncryptedSyntheticsMonitor,
|
||||
monitor: EncryptedSyntheticsMonitor
|
||||
) {
|
||||
return mergeWith({ ...normalizedPreviousMonitor }, monitor, customizer);
|
||||
}
|
||||
|
||||
// Ensure that METADATA is merged deeply, to protect AAD and prevent decryption errors
|
||||
const customizer = (destVal: any, srcValue: any, key: string) => {
|
||||
if (key === ConfigKey.ALERT_CONFIG) {
|
||||
return { ...destVal, ...srcValue };
|
||||
}
|
||||
if (key !== ConfigKey.METADATA) {
|
||||
return srcValue;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeMonitorEmptyValues = (v: any) => {
|
||||
// value is falsy
|
||||
return (
|
||||
v === undefined ||
|
||||
v === null ||
|
||||
// value is empty string
|
||||
(typeof v === 'string' && v.trim() === '') ||
|
||||
// is empty array
|
||||
(Array.isArray(v) && v.length === 0) ||
|
||||
// object is has no values
|
||||
(typeof v === 'object' && Object.keys(v).length === 0)
|
||||
);
|
||||
};
|
|
@ -163,6 +163,7 @@ describe('getNormalizeCommonFields', () => {
|
|||
timeout: '16',
|
||||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -228,6 +229,7 @@ describe('getNormalizeCommonFields', () => {
|
|||
timeout: '16',
|
||||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -94,6 +94,7 @@ export const getNormalizeCommonFields = ({
|
|||
: defaultFields[ConfigKey.PARAMS],
|
||||
// picking out keys specifically, so users can't add arbitrary fields
|
||||
[ConfigKey.ALERT_CONFIG]: getAlertConfig(monitor),
|
||||
[ConfigKey.LABELS]: monitor.fields || defaultFields[ConfigKey.LABELS],
|
||||
};
|
||||
return { normalizedFields, errors };
|
||||
};
|
||||
|
@ -459,7 +460,9 @@ export const flattenAndFormatObject = (obj: Record<string, unknown>, prefix = ''
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
export const normalizeYamlConfig = (monitor: NormalizedProjectProps['monitor']) => {
|
||||
export const normalizeYamlConfig = (data: NormalizedProjectProps['monitor']) => {
|
||||
// we map fields to labels
|
||||
const { fields: _fields, ...monitor } = data;
|
||||
const defaultFields = DEFAULT_FIELDS[monitor.type as MonitorTypeEnum];
|
||||
const supportedKeys = Object.keys(defaultFields);
|
||||
const flattenedConfig = flattenAndFormatObject(monitor, '', supportedKeys);
|
||||
|
|
|
@ -124,5 +124,5 @@ export const formatMaxRedirects = (value?: string | number): string => {
|
|||
|
||||
const defaultFields = DEFAULT_FIELDS[MonitorTypeEnum.HTTP];
|
||||
|
||||
return value ?? defaultFields[ConfigKey.MAX_REDIRECTS];
|
||||
return value ?? String(defaultFields[ConfigKey.MAX_REDIRECTS]);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import expect from '@kbn/expect';
|
|||
import epct from 'expect';
|
||||
import moment from 'moment/moment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { omit, omitBy } from 'lodash';
|
||||
import { omit } from 'lodash';
|
||||
import {
|
||||
ConfigKey,
|
||||
MonitorTypeEnum,
|
||||
|
@ -23,10 +23,7 @@ import { format as formatUrl } from 'url';
|
|||
import supertest from 'supertest';
|
||||
import { getServiceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key';
|
||||
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects';
|
||||
import {
|
||||
removeMonitorEmptyValues,
|
||||
transformPublicKeys,
|
||||
} from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper';
|
||||
import { transformPublicKeys } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/formatters/saved_object_to_monitor';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service';
|
||||
|
@ -54,8 +51,10 @@ export const addMonitorAPIHelper = async (supertestAPI: any, monitor: any, statu
|
|||
return result.body;
|
||||
};
|
||||
|
||||
export const keyToOmitList = ['created_at', 'updated_at', 'id', 'config_id', 'form_monitor_type'];
|
||||
|
||||
export const omitMonitorKeys = (monitor: any) => {
|
||||
return omitBy(transformPublicKeys(monitor), removeMonitorEmptyValues);
|
||||
return omit(transformPublicKeys(monitor), keyToOmitList);
|
||||
};
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
|
@ -154,10 +153,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
const { body: apiResponse } = await addMonitorAPI(newMonitor);
|
||||
|
||||
epct(apiResponse).toEqual(epct.objectContaining({ max_attempts: maxAttempts }));
|
||||
epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: false }));
|
||||
});
|
||||
|
||||
it('can enable retries', async () => {
|
||||
it('can enable retries with max attempts', async () => {
|
||||
const maxAttempts = 2;
|
||||
const newMonitor = {
|
||||
max_attempts: maxAttempts,
|
||||
|
@ -169,7 +168,21 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
const { body: apiResponse } = await addMonitorAPI(newMonitor);
|
||||
|
||||
epct(apiResponse).toEqual(epct.objectContaining({ max_attempts: maxAttempts }));
|
||||
epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: true }));
|
||||
});
|
||||
|
||||
it('can enable retries', async () => {
|
||||
const newMonitor = {
|
||||
retest_on_failure: false,
|
||||
urls: 'https://elastic.co',
|
||||
name: `Sample name ${uuidv4()}`,
|
||||
type: 'http',
|
||||
locations: [localLoc],
|
||||
};
|
||||
|
||||
const { body: apiResponse } = await addMonitorAPI(newMonitor);
|
||||
|
||||
epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: false }));
|
||||
});
|
||||
|
||||
it('cannot create a invalid monitor without a monitor type', async () => {
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
INSTALLED_VERSION,
|
||||
PrivateLocationTestService,
|
||||
} from './services/private_location_test_service';
|
||||
import { addMonitorAPIHelper, omitMonitorKeys } from './add_monitor';
|
||||
import { addMonitorAPIHelper, keyToOmitList, omitMonitorKeys } from './add_monitor';
|
||||
import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
|
@ -211,12 +211,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
|
||||
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
|
||||
|
||||
expect(apiResponse.body).eql(
|
||||
expect(omit(apiResponse.body, keyToOmitList)).eql(
|
||||
omitMonitorKeys({
|
||||
...omit(httpMonitorJson, ['urls']),
|
||||
url: httpMonitorJson.urls,
|
||||
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
|
||||
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
|
||||
updated_at: updatedAt,
|
||||
revision: 2,
|
||||
})
|
||||
|
@ -269,7 +267,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await supertestAPI
|
||||
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId + '?ui=true')
|
||||
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId + '?internal=true')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(httpMonitorJson)
|
||||
.expect(200);
|
||||
|
@ -370,15 +368,11 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
|
||||
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
|
||||
|
||||
expect(apiResponse.body).eql(
|
||||
expect(omit(apiResponse.body, keyToOmitList)).eql(
|
||||
omitMonitorKeys({
|
||||
...monitor,
|
||||
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
|
||||
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
|
||||
[ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID),
|
||||
url: apiResponse.body.url,
|
||||
created_at: createdAt,
|
||||
updated_at: updatedAt,
|
||||
})
|
||||
);
|
||||
monitorId = apiResponse.body.id;
|
||||
|
|
|
@ -170,17 +170,20 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.expect(200);
|
||||
|
||||
const decryptedCreatedMonitor = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id
|
||||
createdMonitorsResponse.body.monitors[0].config_id,
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(decryptedCreatedMonitor.body).to.eql({
|
||||
expect(decryptedCreatedMonitor.rawBody).to.eql({
|
||||
__ui: {
|
||||
script_source: {
|
||||
file_name: '',
|
||||
is_generated_script: false,
|
||||
},
|
||||
},
|
||||
config_id: decryptedCreatedMonitor.body.config_id,
|
||||
config_id: decryptedCreatedMonitor.rawBody.config_id,
|
||||
custom_heartbeat_id: `${journeyId}-${project}-default`,
|
||||
enabled: true,
|
||||
alert: {
|
||||
|
@ -241,6 +244,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
id: `${journeyId}-${project}-default`,
|
||||
hash: 'ekrjelkjrelkjre',
|
||||
max_attempts: 2,
|
||||
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
||||
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
||||
labels: {},
|
||||
});
|
||||
}
|
||||
|
@ -341,17 +346,20 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const decryptedCreatedMonitor = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id
|
||||
const { rawBody: decryptedCreatedMonitor } = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id,
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(decryptedCreatedMonitor.body).to.eql({
|
||||
expect(decryptedCreatedMonitor).to.eql({
|
||||
__ui: {
|
||||
is_tls_enabled: isTLSEnabled,
|
||||
},
|
||||
'check.request.method': 'POST',
|
||||
'check.response.status': ['200'],
|
||||
config_id: decryptedCreatedMonitor.body.config_id,
|
||||
config_id: decryptedCreatedMonitor.config_id,
|
||||
custom_heartbeat_id: `${journeyId}-${project}-default`,
|
||||
'check.response.body.negative': [],
|
||||
'check.response.body.positive': ['${testLocal1}', 'saved'],
|
||||
|
@ -364,8 +372,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
type: 'text',
|
||||
value: '',
|
||||
},
|
||||
params:
|
||||
'{"testLocal1":"testLocalParamsValue","testGlobalParam2":"testGlobalParamOverwrite"}',
|
||||
params: JSON.stringify({
|
||||
testLocal1: 'testLocalParamsValue',
|
||||
testGlobalParam2: 'testGlobalParamOverwrite',
|
||||
}),
|
||||
'check.request.headers': {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
|
@ -427,6 +437,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
ipv4: true,
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
@ -478,15 +490,18 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const decryptedCreatedMonitor = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id
|
||||
const { rawBody: decryptedCreatedMonitor } = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id,
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(decryptedCreatedMonitor.body).to.eql({
|
||||
expect(decryptedCreatedMonitor).to.eql({
|
||||
__ui: {
|
||||
is_tls_enabled: isTLSEnabled,
|
||||
},
|
||||
config_id: decryptedCreatedMonitor.body.config_id,
|
||||
config_id: decryptedCreatedMonitor.config_id,
|
||||
custom_heartbeat_id: `${journeyId}-${project}-default`,
|
||||
'check.receive': '',
|
||||
'check.send': '',
|
||||
|
@ -545,6 +560,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
@ -594,12 +611,15 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const decryptedCreatedMonitor = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id
|
||||
const { rawBody: decryptedCreatedMonitor } = await monitorTestService.getMonitor(
|
||||
createdMonitorsResponse.body.monitors[0].config_id,
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(decryptedCreatedMonitor.body).to.eql({
|
||||
config_id: decryptedCreatedMonitor.body.config_id,
|
||||
expect(decryptedCreatedMonitor).to.eql({
|
||||
config_id: decryptedCreatedMonitor.config_id,
|
||||
custom_heartbeat_id: `${journeyId}-${project}-default`,
|
||||
enabled: true,
|
||||
alert: {
|
||||
|
@ -659,6 +679,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
ipv6: true,
|
||||
params: '',
|
||||
max_attempts: 2,
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
labels: {},
|
||||
});
|
||||
}
|
||||
|
@ -1106,8 +1128,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
const decryptedCreatedMonitor = await monitorTestService.getMonitor(
|
||||
getResponse.body.monitors[0].config_id,
|
||||
true,
|
||||
SPACE_ID
|
||||
{ internal: true, space: SPACE_ID }
|
||||
);
|
||||
const { monitors } = getResponse.body;
|
||||
expect(monitors.length).eql(1);
|
||||
|
@ -1144,8 +1165,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
const decryptedUpdatedMonitor = await monitorTestService.getMonitor(
|
||||
monitorsUpdated[0].config_id,
|
||||
true,
|
||||
SPACE_ID
|
||||
{ internal: true, space: SPACE_ID }
|
||||
);
|
||||
expect(decryptedUpdatedMonitor.body[ConfigKey.SOURCE_PROJECT_CONTENT]).eql(updatedSource);
|
||||
} finally {
|
||||
|
|
|
@ -6,13 +6,8 @@
|
|||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { omitBy } from 'lodash';
|
||||
|
||||
import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
|
||||
import {
|
||||
removeMonitorEmptyValues,
|
||||
transformPublicKeys,
|
||||
} from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper';
|
||||
import { LOCATION_REQUIRED_ERROR } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/monitor_validation';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { addMonitorAPIHelper, omitMonitorKeys } from './add_monitor';
|
||||
|
@ -101,7 +96,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('HTTP Monitor', () => {
|
||||
const defaultFields = omitBy(DEFAULT_FIELDS.http, removeMonitorEmptyValues);
|
||||
const defaultFields = DEFAULT_FIELDS.http;
|
||||
it('return error empty http', async () => {
|
||||
const { message, attributes } = await addMonitorAPI(
|
||||
{
|
||||
|
@ -154,8 +149,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
...monitor,
|
||||
locations: [localLoc],
|
||||
name,
|
||||
max_attempts: 2,
|
||||
retest_on_failure: undefined, // this key is not part of the SO and should not be defined
|
||||
retest_on_failure: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -185,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('TCP Monitor', () => {
|
||||
const defaultFields = omitBy(DEFAULT_FIELDS.tcp, removeMonitorEmptyValues);
|
||||
const defaultFields = DEFAULT_FIELDS.tcp;
|
||||
|
||||
it('base tcp monitor', async () => {
|
||||
const monitor = {
|
||||
|
@ -207,7 +201,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('ICMP Monitor', () => {
|
||||
const defaultFields = omitBy(DEFAULT_FIELDS.icmp, removeMonitorEmptyValues);
|
||||
const defaultFields = DEFAULT_FIELDS.icmp;
|
||||
|
||||
it('base icmp monitor', async () => {
|
||||
const monitor = {
|
||||
|
@ -229,7 +223,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('Browser Monitor', () => {
|
||||
const defaultFields = omitBy(DEFAULT_FIELDS.browser, removeMonitorEmptyValues);
|
||||
const defaultFields = DEFAULT_FIELDS.browser;
|
||||
|
||||
it('empty browser monitor', async () => {
|
||||
const monitor = {
|
||||
|
@ -259,7 +253,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
};
|
||||
const { body: result } = await addMonitorAPI(monitor);
|
||||
|
||||
expect(transformPublicKeys(result)).eql(
|
||||
expect(result).eql(
|
||||
omitMonitorKeys({
|
||||
...defaultFields,
|
||||
...monitor,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import moment from 'moment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { omit, omitBy } from 'lodash';
|
||||
import { omit } from 'lodash';
|
||||
import {
|
||||
ConfigKey,
|
||||
EncryptedSyntheticsSavedMonitor,
|
||||
|
@ -15,7 +15,6 @@ import {
|
|||
} from '@kbn/synthetics-plugin/common/runtime_types';
|
||||
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
||||
import expect from '@kbn/expect';
|
||||
import { removeMonitorEmptyValues } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
import { omitResponseTimestamps, omitEmptyValues } from './helper/monitor';
|
||||
|
@ -40,12 +39,11 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
let testPolicyId = '';
|
||||
|
||||
const saveMonitor = async (monitor: MonitorFields, spaceId?: string) => {
|
||||
const apiURL = spaceId
|
||||
? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`
|
||||
: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS;
|
||||
const res = await supertest
|
||||
.post(
|
||||
spaceId
|
||||
? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`
|
||||
: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS
|
||||
)
|
||||
.post(apiURL + '?internal=true')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(monitor);
|
||||
|
||||
|
@ -55,21 +53,21 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
|
||||
|
||||
return { ...rest, urls: url } as EncryptedSyntheticsSavedMonitor;
|
||||
return rest as EncryptedSyntheticsSavedMonitor;
|
||||
};
|
||||
|
||||
const editMonitor = async (modifiedMonitor: MonitorFields, monitorId: string) => {
|
||||
const res = await supertest
|
||||
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
|
||||
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId + '?internal=true')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(modifiedMonitor);
|
||||
|
||||
expect(res.status).eql(200, JSON.stringify(res.body));
|
||||
|
||||
const { url, ...rest } = res.body;
|
||||
const { created_at: createdAt, updated_at: updatedAt } = res.body;
|
||||
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
|
||||
|
||||
const result = { ...rest, urls: url } as EncryptedSyntheticsSavedMonitor;
|
||||
return omitBy(omit(result, ['created_at', 'updated_at']), removeMonitorEmptyValues);
|
||||
return omit(res.body, ['created_at', 'updated_at']);
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
|
@ -101,9 +99,6 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const savedMonitor = await saveMonitor(newMonitor as MonitorFields);
|
||||
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
|
||||
|
||||
const { created_at: createdAt, updated_at: updatedAt } = savedMonitor;
|
||||
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
|
||||
|
||||
expect(omitResponseTimestamps(savedMonitor)).eql(
|
||||
omitEmptyValues({
|
||||
...newMonitor,
|
||||
|
@ -269,6 +264,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
[ConfigKey.CONFIG_ID]: monitorId,
|
||||
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
|
||||
name: 'test monitor - 12',
|
||||
hash: configHash,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -399,7 +395,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.send(toUpdate)
|
||||
.expect(200);
|
||||
|
||||
const updatedResponse = await monitorTestService.getMonitor(monitorId, true, SPACE_ID);
|
||||
const updatedResponse = await monitorTestService.getMonitor(monitorId, {
|
||||
space: SPACE_ID,
|
||||
internal: true,
|
||||
});
|
||||
|
||||
// ensure monitor was updated
|
||||
expect(updatedResponse.body.urls).eql(toUpdate.urls);
|
||||
|
@ -416,7 +415,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.send(toUpdate2)
|
||||
.expect(200);
|
||||
|
||||
const updatedResponse2 = await monitorTestService.getMonitor(monitorId, true, SPACE_ID);
|
||||
const updatedResponse2 = await monitorTestService.getMonitor(monitorId, {
|
||||
space: SPACE_ID,
|
||||
internal: true,
|
||||
});
|
||||
|
||||
// ensure monitor was updated
|
||||
expect(updatedResponse2.body.urls).eql(toUpdate2.urls);
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { omit, omitBy } from 'lodash';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
|
||||
import { removeMonitorEmptyValues } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper';
|
||||
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
||||
import moment from 'moment';
|
||||
import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types';
|
||||
|
@ -70,7 +69,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
let monitorId = 'test-id';
|
||||
|
||||
const defaultFields = omitBy(DEFAULT_FIELDS.http, removeMonitorEmptyValues);
|
||||
const defaultFields = DEFAULT_FIELDS.http;
|
||||
it('adds test monitor', async () => {
|
||||
const monitor = {
|
||||
type: 'http',
|
||||
|
|
|
@ -55,5 +55,6 @@
|
|||
"ssl.supported_protocols": ["TLSv1.1", "TLSv1.2", "TLSv1.3"],
|
||||
"ssl.verification_mode": "full",
|
||||
"revision": 1,
|
||||
"max_attempts": 2
|
||||
"max_attempts": 2,
|
||||
"labels": {}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
||||
import expect from '@kbn/expect';
|
||||
import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management';
|
||||
import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service';
|
||||
import { omitMonitorKeys } from './add_monitor';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
import { LOCAL_LOCATION } from './get_filters';
|
||||
|
@ -26,6 +28,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const supertest = getService('supertest');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const retry = getService('retry');
|
||||
const monitorTestService = new SyntheticsMonitorTestService(getService);
|
||||
|
||||
let _monitors: MonitorFields[];
|
||||
let monitors: MonitorFields[];
|
||||
|
@ -189,22 +192,45 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
monitors.map((mon) => ({ ...mon, name: mon.name + '4' })).map(saveMonitor)
|
||||
);
|
||||
|
||||
const apiResponse = await supertest
|
||||
.get(
|
||||
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', id1) +
|
||||
'?decrypted=true'
|
||||
)
|
||||
.expect(200);
|
||||
const apiResponse = await monitorTestService.getMonitor(id1);
|
||||
|
||||
expect(apiResponse.body).eql({
|
||||
...monitors[0],
|
||||
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
|
||||
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
|
||||
revision: 1,
|
||||
locations: [LOCAL_LOCATION],
|
||||
name: 'Test HTTP Monitor 044',
|
||||
labels: {},
|
||||
});
|
||||
expect(apiResponse.body).eql(
|
||||
omitMonitorKeys({
|
||||
...monitors[0],
|
||||
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
|
||||
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
|
||||
revision: 1,
|
||||
locations: [LOCAL_LOCATION],
|
||||
name: 'Test HTTP Monitor 044',
|
||||
labels: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get by id with ui query param', async () => {
|
||||
const [{ id: id1 }] = await Promise.all(
|
||||
monitors.map((mon) => ({ ...mon, name: mon.name + '5' })).map(saveMonitor)
|
||||
);
|
||||
|
||||
const apiResponse = await monitorTestService.getMonitor(id1, { internal: true });
|
||||
|
||||
expect(apiResponse.body).eql(
|
||||
omit(
|
||||
{
|
||||
...monitors[0],
|
||||
form_monitor_type: 'icmp',
|
||||
revision: 1,
|
||||
locations: [LOCAL_LOCATION],
|
||||
name: 'Test HTTP Monitor 045',
|
||||
hosts: '192.33.22.111:3333',
|
||||
hash: '',
|
||||
journey_id: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
},
|
||||
['config_id', 'id', 'form_monitor_type']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 404 if monitor id is not found', async () => {
|
||||
|
|
|
@ -5,21 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { omit, omitBy } from 'lodash';
|
||||
import { removeMonitorEmptyValues } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
export function omitResponseTimestamps(monitor: object) {
|
||||
return omitBy(omit(monitor, ['created_at', 'updated_at']), removeMonitorEmptyValues);
|
||||
return omit(monitor, ['created_at', 'updated_at']);
|
||||
}
|
||||
|
||||
export function omitEmptyValues(monitor: object) {
|
||||
const { url, ...rest } = omit(monitor, ['created_at', 'updated_at', 'form_monitor_type']) as any;
|
||||
const { url, ...rest } = omit(monitor, ['created_at', 'updated_at']) as any;
|
||||
|
||||
return omitBy(
|
||||
{
|
||||
...rest,
|
||||
...(url ? { url } : {}),
|
||||
},
|
||||
removeMonitorEmptyValues
|
||||
);
|
||||
return {
|
||||
...rest,
|
||||
...(url ? { url } : {}),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import { MonitorInspectResponse } from '@kbn/synthetics-plugin/public/apps/synth
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import expect from '@kbn/expect';
|
||||
import { ProjectAPIKeyResponse } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/get_api_key';
|
||||
import moment from 'moment/moment';
|
||||
import { omit } from 'lodash';
|
||||
import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
|
@ -45,14 +47,53 @@ export class SyntheticsMonitorTestService {
|
|||
return apiKey;
|
||||
};
|
||||
|
||||
async getMonitor(monitorId: string, decrypted: boolean = true, space?: string) {
|
||||
let url =
|
||||
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId) +
|
||||
(decrypted ? '?decrypted=true' : '');
|
||||
async getMonitor(
|
||||
monitorId: string,
|
||||
{
|
||||
statusCode = 200,
|
||||
space,
|
||||
internal,
|
||||
}: {
|
||||
statusCode?: number;
|
||||
space?: string;
|
||||
internal?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
let url = SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId);
|
||||
if (space) {
|
||||
url = '/s/' + space + url;
|
||||
}
|
||||
return this.supertest.get(url).set('kbn-xsrf', 'true').expect(200);
|
||||
if (internal) {
|
||||
url += `?internal=${internal}`;
|
||||
}
|
||||
const apiResponse = await this.supertest.get(url).expect(200);
|
||||
|
||||
expect(apiResponse.status).eql(statusCode, JSON.stringify(apiResponse.body));
|
||||
|
||||
if (statusCode === 200) {
|
||||
const {
|
||||
created_at: createdAt,
|
||||
updated_at: updatedAt,
|
||||
id,
|
||||
config_id: configId,
|
||||
} = apiResponse.body;
|
||||
expect(id).not.empty();
|
||||
expect(configId).not.empty();
|
||||
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
|
||||
return {
|
||||
rawBody: apiResponse.body,
|
||||
body: {
|
||||
...omit(apiResponse.body, [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'id',
|
||||
'config_id',
|
||||
'form_monitor_type',
|
||||
]),
|
||||
},
|
||||
};
|
||||
}
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async addMonitor(monitor: any) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue