[8.x] [Synthetics] Fixes partial updates for params and params viewing (#195866) (#197850)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Synthetics] Fixes partial updates for params and params viewing
(#195866)](https://github.com/elastic/kibana/pull/195866)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2024-10-25T15:06:52Z","message":"[Synthetics]
Fixes partial updates for params and params viewing (#195866)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/167781\r\n\r\nIn docs we says
that only key/value pairs are required, but in actual\r\nedit, that
means rest of the data was being lost on edits\r\n\r\nAllow partial
updates to params edit API !!\r\n\r\nThis PR makes sure prev objects is
fetched and merged with new data\r\nhence allowing partial updates
!!\r\n\r\nWe are also allowing the ability to view value of the secret
once it's\r\nsaved via API !!\r\n\r\n### Value is hidden\r\nParam value
will not be visible unless user is `super_user` or\r\n`kibana_admin`,
though user can assign new
value.\r\n\r\n---------\r\n\r\nCo-authored-by: Justin Kambic
<jk@elastic.co>","sha":"0ff9a8a9d9ff2bdb99562eeca29152bd0a0c4385","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management"],"title":"[Synthetics]
Fixes partial updates for params and params
viewing","number":195866,"url":"https://github.com/elastic/kibana/pull/195866","mergeCommit":{"message":"[Synthetics]
Fixes partial updates for params and params viewing (#195866)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/167781\r\n\r\nIn docs we says
that only key/value pairs are required, but in actual\r\nedit, that
means rest of the data was being lost on edits\r\n\r\nAllow partial
updates to params edit API !!\r\n\r\nThis PR makes sure prev objects is
fetched and merged with new data\r\nhence allowing partial updates
!!\r\n\r\nWe are also allowing the ability to view value of the secret
once it's\r\nsaved via API !!\r\n\r\n### Value is hidden\r\nParam value
will not be visible unless user is `super_user` or\r\n`kibana_admin`,
though user can assign new
value.\r\n\r\n---------\r\n\r\nCo-authored-by: Justin Kambic
<jk@elastic.co>","sha":"0ff9a8a9d9ff2bdb99562eeca29152bd0a0c4385"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195866","number":195866,"mergeCommit":{"message":"[Synthetics]
Fixes partial updates for params and params viewing (#195866)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/167781\r\n\r\nIn docs we says
that only key/value pairs are required, but in actual\r\nedit, that
means rest of the data was being lost on edits\r\n\r\nAllow partial
updates to params edit API !!\r\n\r\nThis PR makes sure prev objects is
fetched and merged with new data\r\nhence allowing partial updates
!!\r\n\r\nWe are also allowing the ability to view value of the secret
once it's\r\nsaved via API !!\r\n\r\n### Value is hidden\r\nParam value
will not be visible unless user is `super_user` or\r\n`kibana_admin`,
though user can assign new
value.\r\n\r\n---------\r\n\r\nCo-authored-by: Justin Kambic
<jk@elastic.co>","sha":"0ff9a8a9d9ff2bdb99562eeca29152bd0a0c4385"}}]}]
BACKPORT-->

Co-authored-by: Shahzad <shahzad31comp@gmail.com>
This commit is contained in:
Kibana Machine 2024-10-26 03:59:24 +11:00 committed by GitHub
parent 9f5ff8321a
commit 4c22558e57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 357 additions and 112 deletions

View file

@ -26,13 +26,13 @@ You must have `all` privileges for the *Synthetics* feature in the *{observabili
[[parameter-edit-request-body]]
==== Request body
The request body should contain the following attributes:
The request body can contain the following attributes, it can't be empty at least one attribute is required.:
`key`::
(Required, string) The key of the parameter.
(Optional, string) The key of the parameter.
`value`::
(Required, string) The updated value associated with the parameter.
(Optional, string) The updated value associated with the parameter.
`description`::
(Optional, string) The updated description of the parameter.

View file

@ -76,8 +76,8 @@ journey(`GlobalParameters`, async ({ page, params }) => {
await page.click('text=Delete ParameterEdit Parameter >> :nth-match(button, 2)');
await page.click('[aria-label="Key"]');
await page.fill('[aria-label="Key"]', 'username2');
await page.click('[aria-label="Value"]');
await page.fill('[aria-label="Value"]', 'elastic2');
await page.click('[aria-label="New value"]');
await page.fill('[aria-label="New value"]', 'elastic2');
await page.click('.euiComboBox__inputWrap');
await page.fill('[aria-label="Tags"]', 'staging');
await page.press('[aria-label="Tags"]', 'Enter');

View file

@ -0,0 +1,20 @@
/*
* 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 { EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
export function OptionalText() {
return (
<EuiText size="xs" color="subdued">
{i18n.translate('xpack.synthetics.sloEdit.optionalLabel', {
defaultMessage: 'Optional',
})}
</EuiText>
);
}

View file

@ -22,6 +22,7 @@ import { FormProvider } from 'react-hook-form';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { i18n } from '@kbn/i18n';
import { useDispatch, useSelector } from 'react-redux';
import { isEmpty } from 'lodash';
import { NoPermissionsTooltip } from '../../common/components/permissions';
import {
addNewGlobalParamAction,
@ -80,18 +81,29 @@ export const AddParamFlyout = ({
const onSubmit = (formData: SyntheticsParams) => {
const { namespaces, ...paramRequest } = formData;
const shareAcrossSpaces = namespaces?.includes(ALL_SPACES_ID);
const newParamData = {
...paramRequest,
};
if (isEditingItem && id) {
// omit value if it's empty
if (isEmpty(newParamData.value)) {
// @ts-ignore this is a valid check
delete newParamData.value;
}
}
if (isEditingItem && id) {
dispatch(
editGlobalParamAction.get({
id,
paramRequest: { ...paramRequest, share_across_spaces: shareAcrossSpaces },
paramRequest,
})
);
} else {
dispatch(
addNewGlobalParamAction.get({
...paramRequest,
...newParamData,
share_across_spaces: shareAcrossSpaces,
})
);

View file

@ -6,16 +6,11 @@
*/
import React from 'react';
import { ALL_SPACES_ID } from '@kbn/security-plugin/public';
import {
EuiCheckbox,
EuiComboBox,
EuiFieldText,
EuiForm,
EuiFormRow,
EuiTextArea,
} from '@elastic/eui';
import { EuiCheckbox, EuiComboBox, EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Controller, useFormContext, useFormState } from 'react-hook-form';
import { OptionalText } from '../components/optional_text';
import { ParamValueField } from './param_value_field';
import { SyntheticsParams } from '../../../../../../common/runtime_types';
import { ListParamItem } from './params_list';
@ -61,25 +56,8 @@ export const AddParamForm = ({
})}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
label={VALUE_LABEL}
isInvalid={Boolean(errors?.value)}
error={errors?.value?.message}
>
<EuiTextArea
data-test-subj="syntheticsAddParamFormTextArea"
fullWidth
aria-label={VALUE_LABEL}
{...register('value', {
required: {
value: true,
message: VALUE_REQUIRED,
},
})}
/>
</EuiFormRow>
<EuiFormRow fullWidth label={TAGS_LABEL}>
<ParamValueField isEditingItem={isEditingItem} />
<EuiFormRow fullWidth label={TAGS_LABEL} labelAppend={<OptionalText />}>
<Controller
control={control}
name="tags"
@ -102,7 +80,7 @@ export const AddParamForm = ({
)}
/>
</EuiFormRow>
<EuiFormRow fullWidth label={DESCRIPTION_LABEL}>
<EuiFormRow fullWidth label={DESCRIPTION_LABEL} labelAppend={<OptionalText />}>
<EuiFieldText
data-test-subj="syntheticsAddParamFormFieldText"
fullWidth
@ -173,6 +151,6 @@ const KEY_EXISTS = i18n.translate('xpack.synthetics.monitorManagement.param.keyE
defaultMessage: 'Key already exists',
});
const VALUE_REQUIRED = i18n.translate('xpack.synthetics.monitorManagement.value.required', {
export const VALUE_REQUIRED = i18n.translate('xpack.synthetics.monitorManagement.value.required', {
defaultMessage: 'Value is required',
});

View file

@ -0,0 +1,78 @@
/*
* 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 { EuiCallOut, EuiFormRow, EuiSpacer, EuiTextArea } from '@elastic/eui';
import React from 'react';
import { useFormContext, useFormState } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
import { OptionalText } from '../components/optional_text';
import { ListParamItem } from './params_list';
import { SyntheticsParams } from '../../../../../../common/runtime_types';
import { VALUE_LABEL, VALUE_REQUIRED } from './add_param_form';
export const ParamValueField = ({ isEditingItem }: { isEditingItem: ListParamItem | null }) => {
const { register } = useFormContext<SyntheticsParams>();
const { errors } = useFormState<SyntheticsParams>();
if (isEditingItem) {
return (
<>
<EuiFormRow
fullWidth
label={NEW_VALUE_LABEL}
isInvalid={Boolean(errors?.value)}
error={errors?.value?.message}
labelAppend={<OptionalText />}
>
<EuiTextArea
data-test-subj="syntheticsAddParamFormTextArea"
fullWidth
aria-label={NEW_VALUE_LABEL}
{...register('value')}
/>
</EuiFormRow>
<EuiSpacer size="xs" />
<EuiCallOut
size="s"
title={i18n.translate('xpack.synthetics.paramValueField.euiCallOut.newValue', {
defaultMessage:
'Assign a new value to update this parameter, or leave blank to keep the current value.',
})}
iconType="iInCircle"
/>
</>
);
}
return (
<EuiFormRow
fullWidth
label={VALUE_LABEL}
isInvalid={Boolean(errors?.value)}
error={errors?.value?.message}
>
<EuiTextArea
data-test-subj="syntheticsAddParamFormTextArea"
fullWidth
aria-label={VALUE_LABEL}
{...register('value', {
required: {
value: true,
message: VALUE_REQUIRED,
},
})}
/>
</EuiFormRow>
);
};
export const NEW_VALUE_LABEL = i18n.translate(
'xpack.synthetics.monitorManagement.paramForm.newValue',
{
defaultMessage: 'New value',
}
);

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { isEmpty } from 'lodash';
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
import {
DeleteParamsResponse,
@ -35,16 +36,22 @@ export const editGlobalParam = async ({
id,
}: {
id: string;
paramRequest: SyntheticsParamRequest;
}): Promise<SyntheticsParams> =>
apiService.put<SyntheticsParams>(
paramRequest: Partial<SyntheticsParamRequest>;
}): Promise<SyntheticsParams> => {
const data = paramRequest;
if (isEmpty(paramRequest.value)) {
// omit empty value
delete data.value;
}
return await apiService.put<SyntheticsParams>(
SYNTHETICS_API_URLS.PARAMS + `/${id}`,
paramRequest,
data,
SyntheticsParamsCodec,
{
version: INITIAL_REST_VERSION,
}
);
};
export const deleteGlobalParams = async (ids: string[]): Promise<DeleteParamsResponse[]> =>
apiService.delete(

View file

@ -9,6 +9,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { SavedObjectsFindResponse } from '@kbn/core/server';
import { isEmpty } from 'lodash';
import { escapeQuotes } from '@kbn/es-query';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { RouteContext } from './types';
import { MonitorSortFieldSchema } from '../../common/runtime_types/monitor_management/sort_field';
import { getAllLocations } from '../synthetics_service/get_all_locations';
@ -269,3 +270,26 @@ function parseMappingKey(key: string | undefined) {
return key;
}
}
export const validateRouteSpaceName = async (routeContext: RouteContext) => {
const { spaceId, server, request, response } = routeContext;
if (spaceId === DEFAULT_SPACE_ID) {
// default space is always valid
return { spaceId: DEFAULT_SPACE_ID };
}
try {
await server.spaces?.spacesService.getActiveSpace(request);
} catch (error) {
if (error.output?.statusCode === 404) {
return {
spaceId,
invalidResponse: response.notFound({
body: { message: `Kibana space '${spaceId}' does not exist` },
}),
};
}
}
return { invalidResponse: undefined };
};

View file

@ -19,8 +19,12 @@ import { syntheticsParamType } from '../../../../common/types/saved_objects';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
const ParamsObjectSchema = schema.object({
key: schema.string(),
value: schema.string(),
key: schema.string({
minLength: 1,
}),
value: schema.string({
minLength: 1,
}),
description: schema.maybe(schema.string()),
tags: schema.maybe(schema.arrayOf(schema.string())),
share_across_spaces: schema.maybe(schema.boolean()),

View file

@ -6,8 +6,9 @@
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { SavedObject } from '@kbn/core/server';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { SavedObject, SavedObjectsErrorHelpers } from '@kbn/core/server';
import { isEmpty } from 'lodash';
import { validateRouteSpaceName } from '../../common';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { SyntheticsParamRequest, SyntheticsParams } from '../../../../common/runtime_types';
import { syntheticsParamType } from '../../../../common/types/saved_objects';
@ -20,7 +21,7 @@ const RequestParamsSchema = schema.object({
type RequestParams = TypeOf<typeof RequestParamsSchema>;
export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
SyntheticsParams,
SyntheticsParams | undefined,
RequestParams
> = () => ({
method: 'PUT',
@ -30,46 +31,63 @@ export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
request: {
params: RequestParamsSchema,
body: schema.object({
key: schema.string(),
value: schema.string(),
key: schema.maybe(
schema.string({
minLength: 1,
})
),
value: schema.maybe(
schema.string({
minLength: 1,
})
),
description: schema.maybe(schema.string()),
tags: schema.maybe(schema.arrayOf(schema.string())),
share_across_spaces: schema.maybe(schema.boolean()),
}),
},
},
handler: async ({ savedObjectsClient, request, server, response }) => {
try {
const { id: _spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
id: DEFAULT_SPACE_ID,
};
const { id } = request.params;
const { share_across_spaces: _shareAcrossSpaces, ...data } =
request.body as SyntheticsParamRequest & {
id: string;
};
handler: async (routeContext) => {
const { savedObjectsClient, request, response, spaceId, server } = routeContext;
const { invalidResponse } = await validateRouteSpaceName(routeContext);
if (invalidResponse) return invalidResponse;
const { value } = data;
const { id: paramId } = request.params;
const data = request.body as SyntheticsParamRequest;
if (isEmpty(data)) {
return response.badRequest({ body: { message: 'Request body cannot be empty' } });
}
const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
try {
const existingParam =
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsParams>(
syntheticsParamType,
paramId,
{ namespace: spaceId }
);
const newParam = {
...existingParam.attributes,
...data,
};
// value from data since we aren't using encrypted client
const { value } = existingParam.attributes;
const {
id: responseId,
attributes: { key, tags, description },
namespaces,
} = (await savedObjectsClient.update(
} = (await savedObjectsClient.update<SyntheticsParams>(
syntheticsParamType,
id,
data
paramId,
newParam
)) as SavedObject<SyntheticsParams>;
return { id: responseId, key, tags, description, namespaces, value };
} catch (error) {
if (error.output?.statusCode === 404) {
const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
return response.notFound({
body: { message: `Kibana space '${spaceId}' does not exist` },
});
} catch (getErr) {
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
return response.notFound({ body: { message: 'Param not found' } });
}
throw error;
}
},
});

View file

@ -7,7 +7,7 @@
import { SavedObject, SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server';
import { schema, TypeOf } from '@kbn/config-schema';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { RouteContext, SyntheticsRestApiRouteFactory } from '../../types';
import { syntheticsParamType } from '../../../../common/types/saved_objects';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import { SyntheticsParams, SyntheticsParamsReadonly } from '../../../../common/runtime_types';
@ -30,45 +30,13 @@ export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
params: RequestParamsSchema,
},
},
handler: async ({ savedObjectsClient, request, response, server, spaceId }) => {
handler: async (routeContext) => {
const { savedObjectsClient, request, response, spaceId } = routeContext;
try {
const { id: paramId } = request.params;
const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
const canSave =
(
await server.coreStart?.capabilities.resolveCapabilities(request, {
capabilityPath: 'uptime.*',
})
).uptime.save ?? false;
if (canSave) {
if (paramId) {
const savedObject =
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsParams>(
syntheticsParamType,
paramId,
{ namespace: spaceId }
);
return toClientResponse(savedObject);
}
const finder =
await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser<SyntheticsParams>(
{
type: syntheticsParamType,
perPage: 1000,
namespaces: [spaceId],
}
);
const hits: Array<SavedObjectsFindResult<SyntheticsParams>> = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
}
return hits.map((savedObject) => toClientResponse(savedObject));
if (await isAnAdminUser(routeContext)) {
return getDecryptedParams(routeContext, paramId);
} else {
if (paramId) {
const savedObject = await savedObjectsClient.get<SyntheticsParamsReadonly>(
@ -78,11 +46,7 @@ export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
return toClientResponse(savedObject);
}
const data = await savedObjectsClient.find<SyntheticsParamsReadonly>({
type: syntheticsParamType,
perPage: 10000,
});
return data.saved_objects.map((savedObject) => toClientResponse(savedObject));
return findAllParams(routeContext);
}
} catch (error) {
if (error.output?.statusCode === 404) {
@ -94,6 +58,70 @@ export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
},
});
const isAnAdminUser = async (routeContext: RouteContext) => {
const { request, server } = routeContext;
const user = server.coreStart.security.authc.getCurrentUser(request);
const isSuperUser = user?.roles.includes('superuser');
const isAdmin = user?.roles.includes('kibana_admin');
const canSave =
(
await server.coreStart?.capabilities.resolveCapabilities(request, {
capabilityPath: 'uptime.*',
})
).uptime.save ?? false;
return (isSuperUser || isAdmin) && canSave;
};
const getDecryptedParams = async ({ server, spaceId }: RouteContext, paramId?: string) => {
const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
if (paramId) {
const savedObject =
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsParams>(
syntheticsParamType,
paramId,
{ namespace: spaceId }
);
return toClientResponse(savedObject);
}
const finder =
await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser<SyntheticsParams>(
{
type: syntheticsParamType,
perPage: 1000,
namespaces: [spaceId],
}
);
const hits: Array<SavedObjectsFindResult<SyntheticsParams>> = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
}
void finder.close();
return hits.map((savedObject) => toClientResponse(savedObject));
};
const findAllParams = async ({ savedObjectsClient }: RouteContext) => {
const finder = savedObjectsClient.createPointInTimeFinder<SyntheticsParams>({
type: syntheticsParamType,
perPage: 1000,
});
const hits: Array<SavedObjectsFindResult<SyntheticsParams>> = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
}
void finder.close();
return hits.map((savedObject) => toClientResponse(savedObject));
};
const toClientResponse = (
savedObject: SavedObject<SyntheticsParams | SyntheticsParamsReadonly>
) => {

View file

@ -10,6 +10,7 @@ import { pick } from 'lodash';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { syntheticsParamType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service';
import { FtrProviderContext } from '../../ftr_provider_context';
import { PrivateLocationTestService } from './services/private_location_test_service';
@ -21,12 +22,15 @@ export default function ({ getService }: FtrProviderContext) {
describe('AddEditParams', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const kServer = getService('kibanaServer');
const testParam = {
key: 'test',
value: 'test',
};
const testPrivateLocations = new PrivateLocationTestService(getService);
const monitorTestService = new SyntheticsMonitorTestService(getService);
before(async () => {
await testPrivateLocations.installSyntheticsPackage();
@ -93,6 +97,12 @@ export default function ({ getService }: FtrProviderContext) {
const param = getResponse.body[0];
assertHas(param, testParam);
await supertestAPI
.put(SYNTHETICS_API_URLS.PARAMS + '/' + param.id)
.set('kbn-xsrf', 'true')
.send({})
.expect(400);
await supertestAPI
.put(SYNTHETICS_API_URLS.PARAMS + '/' + param.id)
.set('kbn-xsrf', 'true')
@ -107,6 +117,55 @@ export default function ({ getService }: FtrProviderContext) {
assertHas(actualUpdatedParam, expectedUpdatedParam);
});
it('handles partial editing a param', async () => {
const newParam = {
key: 'testUpdated',
value: 'testUpdated',
tags: ['a tag'],
description: 'test description',
};
const response = await supertestAPI
.post(SYNTHETICS_API_URLS.PARAMS)
.set('kbn-xsrf', 'true')
.send(newParam)
.expect(200);
const paramId = response.body.id;
const getResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set('kbn-xsrf', 'true')
.expect(200);
assertHas(getResponse.body, newParam);
await supertestAPI
.put(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set('kbn-xsrf', 'true')
.send({
key: 'testUpdated',
})
.expect(200);
await supertestAPI
.put(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set('kbn-xsrf', 'true')
.send({
key: 'testUpdatedAgain',
value: 'testUpdatedAgain',
})
.expect(200);
const updatedGetResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set('kbn-xsrf', 'true')
.expect(200);
assertHas(updatedGetResponse.body, {
...newParam,
key: 'testUpdatedAgain',
value: 'testUpdatedAgain',
});
});
it('handles spaces', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
@ -277,5 +336,22 @@ export default function ({ getService }: FtrProviderContext) {
expect(getResponse.body[0].namespaces).eql(['*']);
assertHas(getResponse.body[0], testParam);
});
it('should not return values for non admin user', async () => {
const { username, password } = await monitorTestService.addsNewSpace();
const resp = await supertestWithoutAuth
.get(`${SYNTHETICS_API_URLS.PARAMS}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
const params = resp.body;
expect(params.length).to.eql(6);
params.forEach((param: any) => {
expect(param.value).to.eql(undefined);
expect(param.key).to.not.empty();
});
});
});
}