[Synthetics] Isolate Add/Edit API routes HTTP interface from SavedObject type (#162519)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Shahzad <shahzad31comp@gmail.com>
This commit is contained in:
Abdul Wahab Zahid 2023-07-31 10:27:12 +02:00 committed by GitHub
parent 8749d5f006
commit d7e16b39f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 239 additions and 179 deletions

View file

@ -138,10 +138,16 @@ export type ManifestLocation = t.TypeOf<typeof ManifestLocationCodec>;
export type ServiceLocation = t.TypeOf<typeof ServiceLocationCodec>;
export type ServiceLocations = t.TypeOf<typeof ServiceLocationsCodec>;
export type MonitorServiceLocation = t.TypeOf<typeof MonitorServiceLocationCodec>;
export type MonitorServiceLocations = t.TypeOf<typeof MonitorServiceLocationsCodec>;
export type ServiceLocationsApiResponse = t.TypeOf<typeof ServiceLocationsApiResponseCodec>;
export type ServiceLocationErrors = t.TypeOf<typeof ServiceLocationErrors>;
export type ThrottlingOptions = t.TypeOf<typeof ThrottlingOptionsCodec>;
export type Locations = t.TypeOf<typeof LocationsCodec>;
export type PublicLocation = t.TypeOf<typeof PublicLocationCodec>;
export type PublicLocations = t.TypeOf<typeof PublicLocationsCodec>;
export interface ServiceLocationErrorsResponse {
attributes: { message: string; errors: ServiceLocationErrors; id?: string };
}
// TODO: Remove if not needed
// export type MonitorServiceLocations = t.TypeOf<typeof MonitorServiceLocationsCodec>;
// export type ServiceLocationsApiResponse = t.TypeOf<typeof ServiceLocationsApiResponseCodec>;

View file

@ -12,13 +12,13 @@ import { useDispatch, useSelector } from 'react-redux';
import { useSyntheticsRefreshContext } from '../../contexts';
import { cleanMonitorListState, selectServiceLocationsState } from '../../state';
import { showSyncErrors } from '../monitors_page/management/show_sync_errors';
import { createGettingStartedMonitor } from '../../state';
import { createGettingStartedMonitor, UpsertMonitorResponse } from '../../state';
import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults';
import { ConfigKey } from '../../../../../common/constants/monitor_management';
import {
DataStream,
EncryptedSyntheticsSavedMonitor,
ServiceLocationErrors,
SyntheticsMonitorWithId,
} from '../../../../../common/runtime_types';
import {
MONITOR_SUCCESS_LABEL,
@ -56,7 +56,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
}, [monitorData]);
useEffect(() => {
const newMonitor = data as SyntheticsMonitorWithId;
const newMonitor = data as UpsertMonitorResponse;
const hasErrors = data && 'attributes' in data && data.attributes.errors?.length > 0;
if (hasErrors && !loading) {
showSyncErrors(
@ -71,7 +71,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
title: MONITOR_FAILURE_LABEL,
toastLifeTimeMs: 3000,
});
} else if (!loading && newMonitor?.id) {
} else if (!loading && (newMonitor as EncryptedSyntheticsSavedMonitor)?.id) {
kibanaService.toasts.addSuccess({
title: MONITOR_SUCCESS_LABEL,
toastLifeTimeMs: 3000,
@ -82,5 +82,5 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
}
}, [application, data, status, dispatch, loading, refreshApp, serviceLocations]);
return { data: data as SyntheticsMonitorWithId, loading };
return { data: data as EncryptedSyntheticsSavedMonitor, loading };
};

View file

@ -24,10 +24,11 @@ export const MonitorDetailsPanelContainer = (props: Partial<MonitorDetailsPanelP
const { monitor, loading } = useSelectedMonitor();
if (
(latestPing && latestPing?.config_id !== configId) ||
(monitor && monitor[ConfigKey.CONFIG_ID] !== configId)
) {
const isPingRelevant =
latestPing?.config_id === monitor?.[ConfigKey.CONFIG_ID] ||
latestPing?.monitor?.id === monitor?.[ConfigKey.MONITOR_QUERY_ID];
if (!monitor || !isPingRelevant) {
return <EuiSkeletonText lines={6} />;
}

View file

@ -9,7 +9,7 @@ import { useSelector } from 'react-redux';
import { useMemo } from 'react';
import { useEsSearch } from '@kbn/observability-shared-plugin/public';
import { selectEncryptedSyntheticsSavedMonitors } from '../../../state';
import { Ping } from '../../../../../../common/runtime_types';
import { ConfigKey, Ping } from '../../../../../../common/runtime_types';
import {
EXCLUDE_RUN_ONCE_FILTER,
getTimeSpanFilter,
@ -70,7 +70,7 @@ export function useInlineErrors({
const { lastRefresh } = useSyntheticsRefreshContext();
const configIds = syntheticsMonitors.map((monitor) => monitor.id);
const configIds = syntheticsMonitors.map((monitor) => monitor[ConfigKey.CONFIG_ID]);
const doFetch = configIds.length > 0 || onlyInvalidMonitors;

View file

@ -251,7 +251,7 @@ export function MonitorDetailFlyout(props: Props) {
const upsertSuccess = upsertStatus?.status === 'success';
const {
data: monitorSavedObject,
data: monitorObject,
error,
status,
loading,
@ -262,7 +262,7 @@ export function MonitorDetailFlyout(props: Props) {
const monitorDetail = useMonitorDetail(configId, props.location);
const { locations } = useStatusByLocation({
configId,
monitorLocations: monitorSavedObject?.locations,
monitorLocations: monitorObject?.locations,
});
const isOverlay = useIsWithinMaxBreakpoint('xl');
@ -276,14 +276,14 @@ export function MonitorDetailFlyout(props: Props) {
>
{status === FETCH_STATUS.FAILURE && <EuiErrorBoundary>{error?.message}</EuiErrorBoundary>}
{status === FETCH_STATUS.LOADING && <LoadingState />}
{status === FETCH_STATUS.SUCCESS && monitorSavedObject && (
{status === FETCH_STATUS.SUCCESS && monitorObject && (
<>
<EuiFlyoutHeader hasBorder>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h2>{monitorSavedObject?.[ConfigKey.NAME]}</h2>
<h2>{monitorObject?.[ConfigKey.NAME]}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -307,7 +307,7 @@ export function MonitorDetailFlyout(props: Props) {
locations={locations}
setCurrentLocation={setLocation}
configId={configId}
monitor={monitorSavedObject}
monitor={monitorObject}
onEnabledChange={props.onEnabledChange}
/>
</EuiPanel>
@ -320,10 +320,8 @@ export function MonitorDetailFlyout(props: Props) {
latestPing={monitorDetail.data}
configId={configId}
monitor={{
...monitorSavedObject,
...monitorObject,
id,
updated_at: monitorSavedObject.updated_at!,
created_at: monitorSavedObject.created_at!,
}}
loading={Boolean(loading)}
/>

View file

@ -19,3 +19,5 @@ export * from './overview';
export * from './browser_journey';
export * from './ping_status';
export * from './private_locations';
export type { UpsertMonitorResponse } from './monitor_management/api';

View file

@ -108,7 +108,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => {
})
.addCase(enableMonitorAlertAction.success, (state, action) => {
if ('updated_at' in action.payload && state.syntheticsMonitor) {
state.syntheticsMonitor = action.payload.attributes as EncryptedSyntheticsSavedMonitor;
state.syntheticsMonitor = action.payload;
}
});
});

View file

@ -6,10 +6,11 @@
*/
import { createAction } from '@reduxjs/toolkit';
import { UpsertMonitorError, UpsertMonitorRequest, UpsertMonitorResponse } from '..';
import { UpsertMonitorError, UpsertMonitorRequest } from '..';
import {
MonitorManagementListResult,
MonitorFiltersResult,
EncryptedSyntheticsSavedMonitor,
} from '../../../../../common/runtime_types';
import { createAsyncAction } from '../utils/actions';
@ -24,17 +25,16 @@ export const quietFetchMonitorListAction = createAction<MonitorListPageState>(
);
export const fetchUpsertMonitorAction = createAction<UpsertMonitorRequest>('fetchUpsertMonitor');
export const fetchUpsertSuccessAction = createAction<{
id: string;
attributes: { enabled: boolean };
}>('fetchUpsertMonitorSuccess');
export const fetchUpsertSuccessAction = createAction<EncryptedSyntheticsSavedMonitor>(
'fetchUpsertMonitorSuccess'
);
export const fetchUpsertFailureAction = createAction<UpsertMonitorError>(
'fetchUpsertMonitorFailure'
);
export const enableMonitorAlertAction = createAsyncAction<
UpsertMonitorRequest,
UpsertMonitorResponse,
EncryptedSyntheticsSavedMonitor,
UpsertMonitorError
>('enableMonitorAlertAction');

View file

@ -5,15 +5,14 @@
* 2.0.
*/
import { SavedObject } from '@kbn/core-saved-objects-common';
import { UpsertMonitorRequest } from '..';
import { UpsertMonitorResponse } from '../monitor_management/api';
import { SYNTHETICS_API_URLS } from '../../../../../common/constants';
import {
EncryptedSyntheticsMonitor,
FetchMonitorManagementListQueryArgs,
MonitorManagementListResult,
MonitorManagementListResultCodec,
ServiceLocationErrors,
SyntheticsMonitor,
MonitorFiltersResult,
} from '../../../../../common/runtime_types';
@ -56,10 +55,6 @@ export const fetchDeleteMonitor = async ({ configId }: { configId: string }): Pr
return await apiService.delete(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${configId}`);
};
export type UpsertMonitorResponse =
| { attributes: { errors: ServiceLocationErrors }; id: string }
| SavedObject<SyntheticsMonitor>;
export const fetchUpsertMonitor = async ({
monitor,
configId,
@ -75,7 +70,7 @@ export const createGettingStartedMonitor = async ({
monitor,
}: {
monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor;
}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => {
}): Promise<UpsertMonitorResponse> => {
return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, monitor, undefined, {
gettingStarted: true,
});

View file

@ -7,10 +7,9 @@
import { PayloadAction } from '@reduxjs/toolkit';
import { call, put, takeEvery, select, takeLatest, debounce } from 'redux-saga/effects';
import { SavedObject } from '@kbn/core-saved-objects-common';
import { quietFetchOverviewStatusAction } from '../overview_status';
import { enableDefaultAlertingAction } from '../alert_rules';
import { ConfigKey, SyntheticsMonitor } from '../../../../../common/runtime_types';
import { ConfigKey, EncryptedSyntheticsSavedMonitor } from '../../../../../common/runtime_types';
import { kibanaService } from '../../../../utils/kibana_service';
import { MonitorOverviewPageState } from '../overview';
import { quietFetchOverviewAction } from '../overview/actions';
@ -27,12 +26,8 @@ import {
quietFetchMonitorListAction,
fetchMonitorFiltersAction,
} from './actions';
import {
fetchMonitorManagementList,
fetchUpsertMonitor,
fetchMonitorFilters,
UpsertMonitorResponse,
} from './api';
import { fetchMonitorManagementList, fetchUpsertMonitor, fetchMonitorFilters } from './api';
import { toastTitle } from './toast_title';
import { UpsertMonitorRequest } from './models';
@ -54,11 +49,10 @@ export function* enableMonitorAlertEffect() {
function* (action: PayloadAction<UpsertMonitorRequest>): Generator {
try {
const response = yield call(fetchUpsertMonitor, action.payload);
yield put(enableMonitorAlertAction.success(response as UpsertMonitorResponse));
yield put(enableMonitorAlertAction.success(response as EncryptedSyntheticsSavedMonitor));
sendSuccessToast(action.payload.success);
if (
(response as SavedObject<SyntheticsMonitor>).attributes[ConfigKey.ALERT_CONFIG]?.status
?.enabled
(response as EncryptedSyntheticsSavedMonitor)[ConfigKey.ALERT_CONFIG]?.status?.enabled
) {
yield put(enableDefaultAlertingAction.get());
}
@ -81,9 +75,7 @@ export function* upsertMonitorEffect() {
function* (action: PayloadAction<UpsertMonitorRequest>): Generator {
try {
const response = yield call(fetchUpsertMonitor, action.payload);
yield put(
fetchUpsertSuccessAction(response as { id: string; attributes: { enabled: boolean } })
);
yield put(fetchUpsertSuccessAction(response as EncryptedSyntheticsSavedMonitor));
kibanaService.toasts.addSuccess({
title: toastTitle({
title: action.payload.success.message,

View file

@ -79,9 +79,9 @@ export const monitorListReducer = createReducer(initialState, (builder) => {
};
})
.addCase(fetchUpsertSuccessAction, (state, action) => {
state.monitorUpsertStatuses[action.payload.id] = {
state.monitorUpsertStatuses[action.payload.config_id] = {
status: FETCH_STATUS.SUCCESS,
enabled: action.payload.attributes.enabled,
enabled: action.payload.enabled,
};
})
.addCase(fetchUpsertFailureAction, (state, action) => {
@ -94,15 +94,15 @@ export const monitorListReducer = createReducer(initialState, (builder) => {
};
})
.addCase(enableMonitorAlertAction.success, (state, action) => {
state.monitorUpsertStatuses[action.payload.id] = {
...state.monitorUpsertStatuses[action.payload.id],
state.monitorUpsertStatuses[action.payload.config_id] = {
...state.monitorUpsertStatuses[action.payload.config_id],
alertStatus: FETCH_STATUS.SUCCESS,
};
if ('updated_at' in action.payload) {
state.data.monitors = state.data.monitors.map<EncryptedSyntheticsSavedMonitor>(
(monitor: any) => {
if (monitor.config_id === action.payload.id) {
return action.payload.attributes;
if (monitor.config_id === action.payload.config_id) {
return action.payload;
}
return monitor;
}

View file

@ -9,18 +9,19 @@ import { PackagePolicy } from '@kbn/fleet-plugin/common';
import { apiService } from '../../../../utils/api_service';
import {
EncryptedSyntheticsMonitor,
ServiceLocationErrors,
SyntheticsMonitor,
SyntheticsMonitorWithId,
SyntheticsMonitorCodec,
ServiceLocationErrorsResponse,
} from '../../../../../common/runtime_types';
import { SYNTHETICS_API_URLS } from '../../../../../common/constants';
export type UpsertMonitorResponse = ServiceLocationErrorsResponse | EncryptedSyntheticsMonitor;
export const createMonitorAPI = async ({
monitor,
}: {
monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor;
}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => {
}): Promise<UpsertMonitorResponse> => {
return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, monitor);
};
@ -47,7 +48,7 @@ export const updateMonitorAPI = async ({
}: {
monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor;
id: string;
}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitorWithId> => {
}): Promise<UpsertMonitorResponse> => {
return await apiService.put(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor);
};

View file

@ -70,13 +70,13 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => {
state.flyoutConfig = action.payload;
})
.addCase(enableMonitorAlertAction.success, (state, action) => {
const attrs = action.payload.attributes;
if (!('errors' in attrs)) {
const isStatusAlertEnabled = isStatusEnabled(attrs[ConfigKey.ALERT_CONFIG]);
const monitorObject = action.payload;
if (!('errors' in monitorObject)) {
const isStatusAlertEnabled = isStatusEnabled(monitorObject[ConfigKey.ALERT_CONFIG]);
state.data.monitors = state.data.monitors.map((monitor) => {
if (
monitor.id === action.payload.id ||
attrs[ConfigKey.MONITOR_QUERY_ID] === monitor.id
monitor.id === monitorObject[ConfigKey.CONFIG_ID] ||
monitor.id === monitorObject[ConfigKey.MONITOR_QUERY_ID]
) {
return {
...monitor,

View file

@ -37,6 +37,7 @@ import { validateMonitor } from './monitor_validation';
import { sendTelemetryEvents, formatTelemetryEvent } from '../telemetry/monitor_upgrade_sender';
import { formatSecrets } from '../../synthetics_service/utils/secrets';
import { deleteMonitor } from './delete_monitor';
import { mapSavedObjectToMonitor } from './helper';
export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
method: 'POST',
@ -96,7 +97,7 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
initDefaultAlerts(newMonitor.attributes.name, routeContext);
setupGettingStarted(newMonitor.id, routeContext);
return response.ok({ body: newMonitor });
return response.ok({ body: mapSavedObjectToMonitor(newMonitor) });
} catch (getErr) {
server.logger.error(getErr);
if (SavedObjectsErrorHelpers.isForbiddenError(getErr)) {

View file

@ -27,6 +27,7 @@ import {
formatTelemetryUpdateEvent,
} from '../telemetry/monitor_upgrade_sender';
import { formatSecrets, normalizeSecrets } from '../../synthetics_service/utils/secrets';
import { mapSavedObjectToMonitor } from './helper';
// Simplify return promise type and type it with runtime_types
export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
@ -113,7 +114,9 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
});
}
return editedMonitorSavedObject;
return mapSavedObjectToMonitor(
editedMonitorSavedObject as SavedObject<EncryptedSyntheticsMonitorAttributes>
);
} catch (updateErr) {
if (SavedObjectsErrorHelpers.isNotFoundError(updateErr)) {
return getMonitorNotFoundResponse(response, monitorId);

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import expect from '@kbn/expect';
import moment from 'moment/moment';
import { v4 as uuidv4 } from 'uuid';
import { omit } from 'lodash';
import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management';
@ -49,12 +50,17 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.send(newMonitor);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
created_at: createdAt,
updated_at: updatedAt,
},
secretKeys
)
@ -111,13 +117,19 @@ export default function ({ getService }: FtrProviderContext) {
.send(newMonitor);
expect(apiResponse.status).eql(200);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...DEFAULT_FIELDS[DataStream.HTTP],
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
created_at: createdAt,
updated_at: updatedAt,
revision: 1,
},
secretKeys
@ -180,7 +192,12 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
const response = await supertestAPI
.get(SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', apiResponse.body.id))
.get(
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace(
'{monitorId}',
apiResponse.body.config_id
)
)
.set('kbn-xsrf', 'true')
.expect(200);
@ -375,7 +392,7 @@ export default function ({ getService }: FtrProviderContext) {
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
expect(apiResponse.body.attributes[ConfigKey.NAMESPACE]).eql(EXPECTED_NAMESPACE);
expect(apiResponse.body[ConfigKey.NAMESPACE]).eql(EXPECTED_NAMESPACE);
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
@ -423,7 +440,7 @@ export default function ({ getService }: FtrProviderContext) {
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
expect(apiResponse.body.attributes[ConfigKey.NAMESPACE]).eql('default');
expect(apiResponse.body[ConfigKey.NAMESPACE]).eql('default');
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
@ -467,7 +484,7 @@ export default function ({ getService }: FtrProviderContext) {
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
expect(apiResponse.body.attributes[ConfigKey.NAMESPACE]).eql(monitor[ConfigKey.NAMESPACE]);
expect(apiResponse.body[ConfigKey.NAMESPACE]).eql(monitor[ConfigKey.NAMESPACE]);
} finally {
await security.user.delete(username);
await security.role.delete(roleName);

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import semver from 'semver';
import { v4 as uuidv4 } from 'uuid';
import { ConfigKey, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types';
@ -131,12 +132,17 @@ export default function ({ getService }: FtrProviderContext) {
.send(newMonitor)
.expect(200);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
created_at: createdAt,
updated_at: updatedAt,
},
secretKeys
)
@ -185,12 +191,16 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.send(httpMonitorJson);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...httpMonitorJson,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
updated_at: updatedAt,
revision: 2,
},
secretKeys
@ -341,13 +351,18 @@ export default function ({ getService }: FtrProviderContext) {
.send(monitor)
.expect(200);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...monitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
[ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID),
created_at: createdAt,
updated_at: updatedAt,
},
secretKeys
)

View file

@ -1216,7 +1216,7 @@ export default function ({ getService }: FtrProviderContext) {
[ConfigKey.PORT]: 443,
};
const modifiedMonitor = { ...monitors[0]?.attributes, ...updates };
const modifiedMonitor = { ...monitors[0], ...updates };
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitors[0]?.config_id)

View file

@ -5,7 +5,11 @@
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import { HTTPFields, MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types';
import {
EncryptedSyntheticsSavedMonitor,
HTTPFields,
MonitorFields,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
@ -31,7 +35,9 @@ export default function ({ getService }: FtrProviderContext) {
let httpMonitorJson: HTTPFields;
let testPolicyId = '';
const saveMonitor = async (monitor: MonitorFields) => {
const saveMonitor = async (
monitor: MonitorFields
): Promise<EncryptedSyntheticsSavedMonitor> => {
const res = await supertest
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set('kbn-xsrf', 'true')

View file

@ -4,15 +4,20 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { omit } from 'lodash';
import { SimpleSavedObject } from '@kbn/core/public';
import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management';
import { ConfigKey, HTTPFields, MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types';
import {
ConfigKey,
EncryptedSyntheticsSavedMonitor,
HTTPFields,
MonitorFields,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { getFixtureJson } from './helper/get_fixture_json';
import { omitTimestamps, omitTimestampsAndSecrets } from './helper/monitor';
import { PrivateLocationTestService } from './services/private_location_test_service';
import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service';
@ -39,7 +44,7 @@ export default function ({ getService }: FtrProviderContext) {
.send(monitor)
.expect(200);
return res.body as SimpleSavedObject<MonitorFields>;
return res.body as EncryptedSyntheticsSavedMonitor;
};
before(async () => {
@ -68,19 +73,18 @@ export default function ({ getService }: FtrProviderContext) {
it('edits the monitor', async () => {
const newMonitor = httpMonitorJson;
const { id: monitorId, attributes: savedMonitor } = await saveMonitor(
newMonitor as MonitorFields
);
const savedMonitor = await saveMonitor(newMonitor as MonitorFields);
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
expect(savedMonitor).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_ID]: monitorId,
},
secretKeys
)
const { created_at: createdAt, updated_at: updatedAt } = savedMonitor;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(omitTimestamps(savedMonitor)).eql(
omitTimestampsAndSecrets({
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_ID]: monitorId,
})
);
const updates: Partial<HTTPFields> = {
@ -124,27 +128,29 @@ export default function ({ getService }: FtrProviderContext) {
.send(modifiedMonitor)
.expect(200);
expect(editResponse.body.attributes).eql(
omit({ ...modifiedMonitor, revision: 2 }, secretKeys)
expect(omitTimestamps(editResponse.body)).eql(
omitTimestampsAndSecrets({
...modifiedMonitor,
revision: 2,
})
);
});
it('strips unknown keys from monitor edits', async () => {
const newMonitor = httpMonitorJson;
const { id: monitorId, attributes: savedMonitor } = await saveMonitor(
newMonitor as MonitorFields
);
const savedMonitor = await saveMonitor(newMonitor as MonitorFields);
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
expect(savedMonitor).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_ID]: monitorId,
},
secretKeys
)
const { created_at: createdAt, updated_at: updatedAt } = savedMonitor;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(omitTimestamps(savedMonitor)).eql(
omitTimestampsAndSecrets({
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_ID]: monitorId,
})
);
const updates: Partial<HTTPFields> = {
@ -191,17 +197,14 @@ export default function ({ getService }: FtrProviderContext) {
.send(modifiedMonitor)
.expect(200);
expect(editResponse.body.attributes).eql(
omit(
{
...savedMonitor,
...modifiedMonitor,
revision: 2,
},
secretKeys
)
expect(omitTimestamps(editResponse.body)).eql(
omitTimestampsAndSecrets({
...savedMonitor,
...modifiedMonitor,
revision: 2,
})
);
expect(editResponse.body.attributes).not.to.have.keys('unknownkey');
expect(editResponse.body).not.to.have.keys('unknownkey');
});
it('returns 404 if monitor id is not present', async () => {
@ -253,21 +256,21 @@ export default function ({ getService }: FtrProviderContext) {
const newMonitor = httpMonitorJson;
const configHash = 'djrhefje';
const { id: monitorId, attributes: savedMonitor } = await saveMonitor({
const savedMonitor = await saveMonitor({
...(newMonitor as MonitorFields),
[ConfigKey.CONFIG_HASH]: configHash,
});
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(savedMonitor).eql(
omit(
{
...newMonitor,
[ConfigKey.CONFIG_ID]: monitorId,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_HASH]: configHash,
},
secretKeys
)
expect(omitTimestamps(savedMonitor)).eql(
omitTimestampsAndSecrets({
...newMonitor,
[ConfigKey.CONFIG_ID]: monitorId,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_HASH]: configHash,
})
);
const updates: Partial<HTTPFields> = {
@ -289,17 +292,14 @@ export default function ({ getService }: FtrProviderContext) {
.send(modifiedMonitor)
.expect(200);
expect(editResponse.body.attributes).eql(
omit(
{
...modifiedMonitor,
[ConfigKey.CONFIG_ID]: monitorId,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_HASH]: '',
revision: 2,
},
secretKeys
)
expect(omitTimestamps(editResponse.body)).eql(
omitTimestampsAndSecrets({
...modifiedMonitor,
[ConfigKey.CONFIG_ID]: monitorId,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_HASH]: '',
revision: 2,
})
);
expect(editResponse.body).not.to.have.keys('unknownkey');
});
@ -346,8 +346,8 @@ export default function ({ getService }: FtrProviderContext) {
roles: [roleName],
full_name: 'a kibana user',
});
const { id, attributes: savedMonitor } = await saveMonitor(newMonitor as MonitorFields);
monitorId = id;
const savedMonitor = await saveMonitor(newMonitor as MonitorFields);
monitorId = savedMonitor[ConfigKey.CONFIG_ID];
const toUpdate = {
...savedMonitor,
name: '!@#$%^&*()_++[\\-\\]- wow',
@ -393,7 +393,6 @@ export default function ({ getService }: FtrProviderContext) {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
let monitorId = '';
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
@ -403,8 +402,8 @@ export default function ({ getService }: FtrProviderContext) {
.send(newMonitor)
.expect(200);
const { id, attributes: savedMonitor } = response.body;
monitorId = id;
const savedMonitor = response.body;
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
const toUpdate = {
...savedMonitor,
urls: 'https://google.com',

View file

@ -72,12 +72,14 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.send(newMonitor);
expect(apiResponse.body.attributes).eql(
expect(apiResponse.body).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
created_at: apiResponse.body.created_at,
updated_at: apiResponse.body.updated_at,
},
secretKeys
)

View file

@ -33,7 +33,7 @@ export default function ({ getService }: FtrProviderContext) {
.send(monitor)
.expect(200);
return res.body as MonitorFields;
return res.body as EncryptedSyntheticsSavedMonitor;
};
before(async () => {
@ -82,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) {
});
expect(foundMonitors.map((fm) => omit(fm, 'updated_at', 'created_at'))).eql(
expected.map(({ attributes }: any) => attributes)
expected.map((expectedMon) => omit(expectedMon, 'updated_at', 'created_at'))
);
});

View file

@ -5,12 +5,11 @@
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import { SimpleSavedObject } from '@kbn/core/public';
import {
ConfigKey,
SyntheticsMonitor,
MonitorFields,
MonitorOverviewItem,
EncryptedSyntheticsSavedMonitor,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
@ -52,7 +51,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.send(monitor);
return res.body as SimpleSavedObject<MonitorFields>;
return res.body as EncryptedSyntheticsSavedMonitor;
};
before(async () => {
@ -81,7 +80,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(200);
await Promise.all([
(body.monitors as Array<SimpleSavedObject<MonitorFields>>).map((monitor) => {
(body.monitors as EncryptedSyntheticsSavedMonitor[]).map((monitor) => {
return deleteMonitor(monitor.id);
}),
]);
@ -106,10 +105,9 @@ export default function ({ getService }: FtrProviderContext) {
});
it('returns the correct response', async () => {
let savedMonitors: SimpleSavedObject[] = [];
let savedMonitors: EncryptedSyntheticsSavedMonitor[] = [];
try {
const savedResponse = await Promise.all(monitors.map(saveMonitor));
savedMonitors = savedResponse;
savedMonitors = await Promise.all(monitors.map(saveMonitor));
const apiResponse = await supertest.get(
`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_OVERVIEW}`
@ -130,10 +128,9 @@ export default function ({ getService }: FtrProviderContext) {
});
it('accepts search queries', async () => {
let savedMonitors: Array<SimpleSavedObject<SyntheticsMonitor>> = [];
let savedMonitors: EncryptedSyntheticsSavedMonitor[] = [];
try {
const savedResponse = await Promise.all(monitors.map(saveMonitor));
savedMonitors = savedResponse;
savedMonitors = await Promise.all(monitors.map(saveMonitor));
const apiResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_OVERVIEW}`)
@ -144,7 +141,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(apiResponse.body.total).eql(2);
expect(apiResponse.body.allMonitorIds.sort()).eql(
savedMonitors
.filter((monitor) => monitor.attributes.name.includes('19'))
.filter((monitor) => monitor.name.includes('19'))
.map((monitor) => monitor.id)
);
expect(apiResponse.body.monitors.length).eql(2);
@ -157,11 +154,11 @@ export default function ({ getService }: FtrProviderContext) {
}
});
it('returns the correct response', async () => {
let savedMonitors: Array<SimpleSavedObject<SyntheticsMonitor>> = [];
it('returns the correct response for customHeartbeatId', async () => {
let savedMonitors: EncryptedSyntheticsSavedMonitor[] = [];
const customHeartbeatId = 'example_custom_heartbeat_id';
try {
const savedResponse = await Promise.all(
savedMonitors = await Promise.all(
[
{ ...monitors[0], name: 'test monitor a' },
{
@ -171,7 +168,6 @@ export default function ({ getService }: FtrProviderContext) {
},
].map(saveMonitor)
);
savedMonitors = savedResponse;
const apiResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_OVERVIEW}`)
@ -179,8 +175,8 @@ export default function ({ getService }: FtrProviderContext) {
const expected: MonitorOverviewItem[] = [
{
id: savedMonitors[0].attributes[ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[0].id,
id: savedMonitors[0][ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[0].config_id,
name: 'test monitor a',
location: {
id: 'eu-west-01',
@ -199,8 +195,8 @@ export default function ({ getService }: FtrProviderContext) {
schedule: '5',
},
{
id: savedMonitors[0].attributes[ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[0].id,
id: savedMonitors[0][ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[0].config_id,
name: 'test monitor a',
location: {
id: 'eu-west-02',
@ -219,8 +215,8 @@ export default function ({ getService }: FtrProviderContext) {
schedule: '5',
},
{
id: savedMonitors[1].attributes[ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[1].id,
id: savedMonitors[1][ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[1].config_id,
name: 'test monitor b',
location: {
id: 'eu-west-01',
@ -239,8 +235,8 @@ export default function ({ getService }: FtrProviderContext) {
schedule: '5',
},
{
id: savedMonitors[1].attributes[ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[1].id,
id: savedMonitors[1][ConfigKey.MONITOR_QUERY_ID],
configId: savedMonitors[1].config_id,
name: 'test monitor b',
location: {
id: 'eu-west-02',
@ -261,11 +257,11 @@ export default function ({ getService }: FtrProviderContext) {
];
expect(apiResponse.body.monitors).eql(expected);
expect(savedMonitors[1].attributes[ConfigKey.MONITOR_QUERY_ID]).eql(customHeartbeatId);
expect(savedMonitors[1][ConfigKey.MONITOR_QUERY_ID]).eql(customHeartbeatId);
} finally {
await Promise.all(
savedMonitors.map((monitor) => {
return deleteMonitor(monitor.id);
return deleteMonitor(monitor.config_id);
})
);
}

View file

@ -0,0 +1,17 @@
/*
* 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 { omit } from 'lodash';
import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management';
export function omitTimestamps(monitor: object) {
return omit(monitor, ['created_at', 'updated_at']);
}
export function omitTimestampsAndSecrets(monitor: object) {
return omit(monitor, ['created_at', 'updated_at', ...secretKeys]);
}

View file

@ -7,8 +7,7 @@
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { SavedObject } from '@kbn/core-saved-objects-common/src/server_types';
import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types';
import { EncryptedSyntheticsSavedMonitor } from '@kbn/synthetics-plugin/common/runtime_types';
import { MonitorInspectResponse } from '@kbn/synthetics-plugin/public/apps/synthetics/state/monitor_management/api';
import { v4 as uuidv4 } from 'uuid';
import { FtrProviderContext } from '../../../ftr_provider_context';
@ -40,7 +39,7 @@ export class SyntheticsMonitorTestService {
.send(monitor)
.expect(200);
return res.body as SavedObject<MonitorFields>;
return res.body as EncryptedSyntheticsSavedMonitor;
}
async inspectMonitor(monitor: any, hideParams: boolean = true) {

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import {
ConfigKey,
HTTPFields,
@ -103,12 +104,17 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.send(newMonitor);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
created_at: createdAt,
updated_at: updatedAt,
},
secretKeys
)
@ -210,12 +216,17 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.send(newMonitor);
expect(apiResponse.body.attributes).eql(
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(apiResponse.body).eql(
omit(
{
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
created_at: createdAt,
updated_at: updatedAt,
},
secretKeys
)

View file

@ -125,7 +125,6 @@
"@kbn/observability-shared-plugin",
"@kbn/maps-vector-tile-utils",
"@kbn/server-route-repository",
"@kbn/core-saved-objects-common",
"@kbn/core-http-common",
"@kbn/slo-schema",
"@kbn/lens-plugin",