mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
Compare commits
1 commit
main
...
deploy-fix
Author | SHA1 | Date | |
---|---|---|---|
|
0cb63c4ee0 |
41 changed files with 124 additions and 1079 deletions
|
@ -56864,69 +56864,6 @@ paths:
|
||||||
summary: Get a private location
|
summary: Get a private location
|
||||||
tags:
|
tags:
|
||||||
- synthetics
|
- synthetics
|
||||||
put:
|
|
||||||
description: |
|
|
||||||
Update an existing private location's label.
|
|
||||||
You must have `all` privileges for the Synthetics and Uptime feature in the Observability section of the Kibana feature privileges.
|
|
||||||
When a private location's label is updated, all monitors using this location will also be updated to maintain data consistency.
|
|
||||||
operationId: put-private-location
|
|
||||||
parameters:
|
|
||||||
- description: The unique identifier of the private location to be updated.
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
putPrivateLocationRequestExample1:
|
|
||||||
description: Update a private location's label.
|
|
||||||
value: |-
|
|
||||||
{
|
|
||||||
"label": "Updated Private Location Name"
|
|
||||||
}
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
label:
|
|
||||||
description: A new label for the private location. Must be at least 1 character long.
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- label
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
putPrivateLocationResponseExample1:
|
|
||||||
value: |-
|
|
||||||
{
|
|
||||||
"label": "Updated Private Location Name",
|
|
||||||
"id": "test-private-location-id",
|
|
||||||
"agentPolicyId": "test-private-location-id",
|
|
||||||
"isServiceManaged": false,
|
|
||||||
"isInvalid": false,
|
|
||||||
"tags": ["private", "testing", "updated"],
|
|
||||||
"geo": {
|
|
||||||
"lat": 37.7749,
|
|
||||||
"lon": -122.4194
|
|
||||||
},
|
|
||||||
"spaces": ["*"]
|
|
||||||
}
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Synthetics_getPrivateLocation'
|
|
||||||
description: A successful response.
|
|
||||||
'400':
|
|
||||||
description: If the `label` is shorter than 1 character the API will return a 400 Bad Request response with a corresponding error message.
|
|
||||||
'404':
|
|
||||||
description: If the private location with the specified ID does not exist, the API will return a 404 Not Found response.
|
|
||||||
summary: Update a private location
|
|
||||||
tags:
|
|
||||||
- synthetics
|
|
||||||
/api/task_manager/_health:
|
/api/task_manager/_health:
|
||||||
get:
|
get:
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -53,7 +53,6 @@ import {
|
||||||
expectError,
|
expectError,
|
||||||
createBadRequestErrorPayload,
|
createBadRequestErrorPayload,
|
||||||
expectUpdateResult,
|
expectUpdateResult,
|
||||||
MULTI_NAMESPACE_TYPE,
|
|
||||||
} from '../../test_helpers/repository.test.common';
|
} from '../../test_helpers/repository.test.common';
|
||||||
import type { ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server';
|
import type { ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server';
|
||||||
import { savedObjectsExtensionsMock } from '../../mocks/saved_objects_extensions.mock';
|
import { savedObjectsExtensionsMock } from '../../mocks/saved_objects_extensions.mock';
|
||||||
|
@ -621,74 +620,6 @@ describe('#bulkUpdate', () => {
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('migrates single namespace objects using the object namespace', async () => {
|
|
||||||
const modifiedObj2 = {
|
|
||||||
...obj2,
|
|
||||||
coreMigrationVersion: '8.0.0',
|
|
||||||
namespace: 'test',
|
|
||||||
};
|
|
||||||
const objects = [modifiedObj2];
|
|
||||||
migrator.migrateDocument.mockImplementationOnce((doc) => ({ ...doc, migrated: true }));
|
|
||||||
|
|
||||||
await bulkUpdateSuccess(client, repository, registry, objects);
|
|
||||||
|
|
||||||
expect(migrator.migrateDocument).toHaveBeenCalledTimes(2);
|
|
||||||
expectMigrationArgs(
|
|
||||||
{
|
|
||||||
id: modifiedObj2.id,
|
|
||||||
namespace: 'test',
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('migrates multiple namespace objects using the object namespaces', async () => {
|
|
||||||
const modifiedObj2 = {
|
|
||||||
...obj2,
|
|
||||||
type: MULTI_NAMESPACE_TYPE,
|
|
||||||
coreMigrationVersion: '8.0.0',
|
|
||||||
namespace: 'test',
|
|
||||||
};
|
|
||||||
const objects = [modifiedObj2];
|
|
||||||
migrator.migrateDocument.mockImplementationOnce((doc) => ({ ...doc, migrated: true }));
|
|
||||||
|
|
||||||
await bulkUpdateSuccess(client, repository, registry, objects);
|
|
||||||
|
|
||||||
expect(migrator.migrateDocument).toHaveBeenCalledTimes(2);
|
|
||||||
expectMigrationArgs(
|
|
||||||
{
|
|
||||||
id: modifiedObj2.id,
|
|
||||||
namespaces: ['test'],
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('migrates namespace agnsostic objects', async () => {
|
|
||||||
const modifiedObj2 = {
|
|
||||||
...obj2,
|
|
||||||
type: NAMESPACE_AGNOSTIC_TYPE,
|
|
||||||
coreMigrationVersion: '8.0.0',
|
|
||||||
namespace: 'test', // specify a namespace, but it should be ignored
|
|
||||||
};
|
|
||||||
const objects = [modifiedObj2];
|
|
||||||
migrator.migrateDocument.mockImplementationOnce((doc) => ({ ...doc, migrated: true }));
|
|
||||||
|
|
||||||
await bulkUpdateSuccess(client, repository, registry, objects);
|
|
||||||
|
|
||||||
expect(migrator.migrateDocument).toHaveBeenCalledTimes(2);
|
|
||||||
expectMigrationArgs(
|
|
||||||
{
|
|
||||||
id: modifiedObj2.id,
|
|
||||||
namespaces: [],
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('returns', () => {
|
describe('returns', () => {
|
||||||
|
|
|
@ -150,11 +150,17 @@ export const performBulkUpdate = async <T>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `objectNamespace` is a namespace string, while `namespace` is a namespace ID.
|
||||||
|
// The object namespace string, if defined, will supersede the operation's namespace ID.
|
||||||
|
const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace);
|
||||||
|
|
||||||
const getNamespaceId = (objectNamespace?: string) =>
|
const getNamespaceId = (objectNamespace?: string) =>
|
||||||
objectNamespace !== undefined
|
objectNamespace !== undefined
|
||||||
? SavedObjectsUtils.namespaceStringToId(objectNamespace)
|
? SavedObjectsUtils.namespaceStringToId(objectNamespace)
|
||||||
: namespace;
|
: namespace;
|
||||||
|
|
||||||
|
const getNamespaceString = (objectNamespace?: string) => objectNamespace ?? namespaceString;
|
||||||
|
|
||||||
const bulkGetDocs = validObjects.map(({ value: { type, id, objectNamespace } }) => ({
|
const bulkGetDocs = validObjects.map(({ value: { type, id, objectNamespace } }) => ({
|
||||||
_id: serializer.generateRawId(getNamespaceId(objectNamespace), type, id),
|
_id: serializer.generateRawId(getNamespaceId(objectNamespace), type, id),
|
||||||
_index: commonHelper.getIndexForType(type),
|
_index: commonHelper.getIndexForType(type),
|
||||||
|
@ -229,6 +235,7 @@ export const performBulkUpdate = async <T>(
|
||||||
mergeAttributes,
|
mergeAttributes,
|
||||||
} = expectedBulkGetResult.value;
|
} = expectedBulkGetResult.value;
|
||||||
|
|
||||||
|
let namespaces: string[] | undefined;
|
||||||
const versionProperties = getExpectedVersionProperties(version);
|
const versionProperties = getExpectedVersionProperties(version);
|
||||||
const indexFound = bulkGetResponse?.statusCode !== 404;
|
const indexFound = bulkGetResponse?.statusCode !== 404;
|
||||||
const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined;
|
const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined;
|
||||||
|
@ -251,18 +258,15 @@ export const performBulkUpdate = async <T>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let savedObjectNamespace: string | undefined;
|
|
||||||
let savedObjectNamespaces: string[] | undefined;
|
|
||||||
|
|
||||||
if (isMultiNS) {
|
if (isMultiNS) {
|
||||||
// @ts-expect-error MultiGetHit is incorrectly missing _id, _source
|
// @ts-expect-error MultiGetHit is incorrectly missing _id, _source
|
||||||
savedObjectNamespaces = actualResult!._source.namespaces ?? [
|
namespaces = actualResult!._source.namespaces ?? [
|
||||||
// @ts-expect-error MultiGetHit is incorrectly missing _id, _source
|
// @ts-expect-error MultiGetHit is incorrectly missing _id, _source
|
||||||
SavedObjectsUtils.namespaceIdToString(actualResult!._source.namespace),
|
SavedObjectsUtils.namespaceIdToString(actualResult!._source.namespace),
|
||||||
];
|
];
|
||||||
} else if (registry.isSingleNamespace(type)) {
|
} else if (registry.isSingleNamespace(type)) {
|
||||||
// if `objectNamespace` is undefined, fall back to `options.namespace`
|
// if `objectNamespace` is undefined, fall back to `options.namespace`
|
||||||
savedObjectNamespace = objectNamespace ?? namespace;
|
namespaces = [getNamespaceString(objectNamespace)];
|
||||||
}
|
}
|
||||||
|
|
||||||
const document = getSavedObjectFromSource<T>(
|
const document = getSavedObjectFromSource<T>(
|
||||||
|
@ -306,8 +310,8 @@ export const performBulkUpdate = async <T>(
|
||||||
...migrated!,
|
...migrated!,
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
...(savedObjectNamespace && { namespace: savedObjectNamespace }),
|
namespace,
|
||||||
...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }),
|
namespaces,
|
||||||
attributes: updatedAttributes,
|
attributes: updatedAttributes,
|
||||||
updated_at: time,
|
updated_at: time,
|
||||||
updated_by: updatedBy,
|
updated_by: updatedBy,
|
||||||
|
@ -317,9 +321,6 @@ export const performBulkUpdate = async <T>(
|
||||||
migratedUpdatedSavedObjectDoc as SavedObjectSanitizedDoc
|
migratedUpdatedSavedObjectDoc as SavedObjectSanitizedDoc
|
||||||
);
|
);
|
||||||
|
|
||||||
const namespaces =
|
|
||||||
savedObjectNamespaces ?? (savedObjectNamespace ? [savedObjectNamespace] : []);
|
|
||||||
|
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -546,13 +546,9 @@ describe('#update', () => {
|
||||||
namespace: 'default',
|
namespace: 'default',
|
||||||
});
|
});
|
||||||
expect(client.index).toHaveBeenCalledWith(
|
expect(client.index).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({ id: expect.stringMatching(`${type}:${id}`) }),
|
||||||
id: expect.stringMatching(`${type}:${id}`),
|
|
||||||
}),
|
|
||||||
expect.anything()
|
expect.anything()
|
||||||
);
|
);
|
||||||
// Assert that 'namespace' does not exist at all
|
|
||||||
expect(client.index.mock.calls[0][0]).not.toHaveProperty('namespace');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`doesn't prepend namespace to the id when using agnostic-namespace type`, async () => {
|
it(`doesn't prepend namespace to the id when using agnostic-namespace type`, async () => {
|
||||||
|
|
|
@ -716,7 +716,7 @@ export const expectUpdateResult = ({
|
||||||
attributes,
|
attributes,
|
||||||
references,
|
references,
|
||||||
version: mockVersion,
|
version: mockVersion,
|
||||||
namespaces: [],
|
namespaces: ['default'],
|
||||||
...mockTimestampFields,
|
...mockTimestampFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const GETTING_STARTED_ROUTE = '/monitors/getting-started';
|
||||||
|
|
||||||
export const SETTINGS_ROUTE = '/settings';
|
export const SETTINGS_ROUTE = '/settings';
|
||||||
|
|
||||||
export const PRIVATE_LOCATIONS_ROUTE = '/settings/private-locations';
|
export const PRIVATE_LOCATIOSN_ROUTE = '/settings/private-locations';
|
||||||
|
|
||||||
export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId';
|
export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId';
|
||||||
|
|
||||||
|
|
|
@ -1024,71 +1024,6 @@ paths:
|
||||||
},
|
},
|
||||||
"namespace": "default"
|
"namespace": "default"
|
||||||
}
|
}
|
||||||
put:
|
|
||||||
summary: Update a private location
|
|
||||||
operationId: put-private-location
|
|
||||||
description: >
|
|
||||||
Update an existing private location's label.
|
|
||||||
|
|
||||||
You must have `all` privileges for the Synthetics and Uptime feature in the Observability section of the Kibana feature privileges.
|
|
||||||
|
|
||||||
When a private location's label is updated, all monitors using this location will also be updated to maintain data consistency.
|
|
||||||
tags:
|
|
||||||
- synthetics
|
|
||||||
parameters:
|
|
||||||
- in: path
|
|
||||||
name: id
|
|
||||||
description: The unique identifier of the private location to be updated.
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- label
|
|
||||||
properties:
|
|
||||||
label:
|
|
||||||
type: string
|
|
||||||
minLength: 1
|
|
||||||
description: A new label for the private location. Must be at least 1 character long.
|
|
||||||
examples:
|
|
||||||
putPrivateLocationRequestExample1:
|
|
||||||
description: Update a private location's label.
|
|
||||||
value: |-
|
|
||||||
{
|
|
||||||
"label": "Updated Private Location Name"
|
|
||||||
}
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: A successful response.
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/getPrivateLocation"
|
|
||||||
examples:
|
|
||||||
putPrivateLocationResponseExample1:
|
|
||||||
value: |-
|
|
||||||
{
|
|
||||||
"label": "Updated Private Location Name",
|
|
||||||
"id": "test-private-location-id",
|
|
||||||
"agentPolicyId": "test-private-location-id",
|
|
||||||
"isServiceManaged": false,
|
|
||||||
"isInvalid": false,
|
|
||||||
"tags": ["private", "testing", "updated"],
|
|
||||||
"geo": {
|
|
||||||
"lat": 37.7749,
|
|
||||||
"lon": -122.4194
|
|
||||||
},
|
|
||||||
"spaces": ["*"]
|
|
||||||
}
|
|
||||||
'400':
|
|
||||||
description: If the `label` is shorter than 1 character the API will return a 400 Bad Request response with a corresponding error message.
|
|
||||||
'404':
|
|
||||||
description: If the private location with the specified ID does not exist, the API will return a 404 Not Found response.
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
commonMonitorFields:
|
commonMonitorFields:
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { syntheticsAppPageProvider } from '../page_objects/synthetics_app';
|
||||||
journey(`PrivateLocationsSettings`, async ({ page, params }) => {
|
journey(`PrivateLocationsSettings`, async ({ page, params }) => {
|
||||||
const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl, params });
|
const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl, params });
|
||||||
const services = new SyntheticsServices(params);
|
const services = new SyntheticsServices(params);
|
||||||
const NEW_LOCATION_LABEL = 'Updated Test Location';
|
|
||||||
|
|
||||||
page.setDefaultTimeout(2 * 30000);
|
page.setDefaultTimeout(2 * 30000);
|
||||||
|
|
||||||
|
@ -88,33 +87,11 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
step('Edit private location label and verify disabled fields', async () => {
|
|
||||||
// Click on the edit button for the location
|
|
||||||
await page.click('[data-test-subj="action-edit"]');
|
|
||||||
|
|
||||||
// Verify that agent policy selector is disabled
|
|
||||||
expect(await page.locator('[aria-label="Select agent policy"]').isDisabled()).toBe(true);
|
|
||||||
|
|
||||||
// Verify that tags field is disabled
|
|
||||||
expect(await page.locator('[aria-label="Tags"]').isDisabled()).toBe(false);
|
|
||||||
|
|
||||||
// Verify that spaces selector is disabled
|
|
||||||
expect(await page.locator('[aria-label="Spaces "]').isDisabled()).toBe(true);
|
|
||||||
|
|
||||||
await page.fill('[aria-label="Location name"]', NEW_LOCATION_LABEL);
|
|
||||||
|
|
||||||
// Save the changes
|
|
||||||
await page.click('[data-test-subj="syntheticsLocationFlyoutSaveButton"]');
|
|
||||||
|
|
||||||
// Wait for the save to complete and verify the updated label appears in the table
|
|
||||||
await page.waitForSelector(`td:has-text("${NEW_LOCATION_LABEL}")`);
|
|
||||||
});
|
|
||||||
|
|
||||||
step('Integration cannot be edited in Fleet', async () => {
|
step('Integration cannot be edited in Fleet', async () => {
|
||||||
await page.goto(`${params.kibanaUrl}/app/integrations/detail/synthetics/policies`);
|
await page.goto(`${params.kibanaUrl}/app/integrations/detail/synthetics/policies`);
|
||||||
await page.waitForSelector('h1:has-text("Elastic Synthetics")');
|
await page.waitForSelector('h1:has-text("Elastic Synthetics")');
|
||||||
|
|
||||||
await page.click(`text="test-monitor-${NEW_LOCATION_LABEL}-default"`);
|
await page.click('text="test-monitor-Test private-default"');
|
||||||
await page.waitForSelector('h1:has-text("Edit Elastic Synthetics integration")');
|
await page.waitForSelector('h1:has-text("Edit Elastic Synthetics integration")');
|
||||||
await page.waitForSelector('text="This package policy is managed by the Synthetics app."');
|
await page.waitForSelector('text="This package policy is managed by the Synthetics app."');
|
||||||
});
|
});
|
||||||
|
@ -134,14 +111,14 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => {
|
||||||
await page.click('h1:has-text("Settings")');
|
await page.click('h1:has-text("Settings")');
|
||||||
await page.click('text=Private Locations');
|
await page.click('text=Private Locations');
|
||||||
await page.waitForSelector('td:has-text("1")');
|
await page.waitForSelector('td:has-text("1")');
|
||||||
await page.waitForSelector(`td:has-text("${NEW_LOCATION_LABEL}")`);
|
await page.waitForSelector('td:has-text("Test private")');
|
||||||
await page.click('.euiTableRowCell .euiToolTipAnchor');
|
await page.click('.euiTableRowCell .euiToolTipAnchor');
|
||||||
await page.click('button:has-text("Tags")');
|
await page.click('button:has-text("Tags")');
|
||||||
await page.click('[aria-label="Tags"] >> text=Area51');
|
await page.click('[aria-label="Tags"] >> text=Area51');
|
||||||
await page.click(
|
await page.click(
|
||||||
'main div:has-text("Private locations allow you to run monitors from your own premises. They require")'
|
'main div:has-text("Private locations allow you to run monitors from your own premises. They require")'
|
||||||
);
|
);
|
||||||
await page.click(`text=${NEW_LOCATION_LABEL}`);
|
await page.click('text=Test private');
|
||||||
|
|
||||||
await page.click('.euiTableRowCell .euiToolTipAnchor');
|
await page.click('.euiTableRowCell .euiToolTipAnchor');
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,8 @@ describe('GettingStartedPage', () => {
|
||||||
loading: false,
|
loading: false,
|
||||||
privateLocations: [],
|
privateLocations: [],
|
||||||
deleteLoading: false,
|
deleteLoading: false,
|
||||||
onCreateLocationAPI: jest.fn(),
|
onSubmit: jest.fn(),
|
||||||
onDeleteLocationAPI: jest.fn(),
|
onDelete: jest.fn(),
|
||||||
onEditLocationAPI: jest.fn(),
|
|
||||||
createLoading: false,
|
createLoading: false,
|
||||||
});
|
});
|
||||||
jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true);
|
jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true);
|
||||||
|
@ -83,7 +82,7 @@ describe('GettingStartedPage', () => {
|
||||||
loading: false,
|
loading: false,
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: true,
|
isCreatePrivateLocationFlyoutVisible: true,
|
||||||
},
|
},
|
||||||
agentPolicies: {
|
agentPolicies: {
|
||||||
data: [],
|
data: [],
|
||||||
|
@ -113,7 +112,7 @@ describe('GettingStartedPage', () => {
|
||||||
data: [{}],
|
data: [{}],
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: true,
|
isCreatePrivateLocationFlyoutVisible: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -152,7 +151,7 @@ describe('GettingStartedPage', () => {
|
||||||
data: [{}],
|
data: [{}],
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: true,
|
isCreatePrivateLocationFlyoutVisible: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,11 @@ import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_
|
||||||
import { getServiceLocations, cleanMonitorListState } from '../../state';
|
import { getServiceLocations, cleanMonitorListState } from '../../state';
|
||||||
import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui';
|
import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui';
|
||||||
import { SimpleMonitorForm } from './simple_monitor_form';
|
import { SimpleMonitorForm } from './simple_monitor_form';
|
||||||
import {
|
import { AddLocationFlyout, NewLocation } from '../settings/private_locations/add_location_flyout';
|
||||||
AddOrEditLocationFlyout,
|
|
||||||
NewLocation,
|
|
||||||
} from '../settings/private_locations/add_or_edit_location_flyout';
|
|
||||||
import type { ClientPluginsStart } from '../../../../plugin';
|
import type { ClientPluginsStart } from '../../../../plugin';
|
||||||
import { getAgentPoliciesAction, selectAgentPolicies } from '../../state/agent_policies';
|
import { getAgentPoliciesAction, selectAgentPolicies } from '../../state/agent_policies';
|
||||||
import { setIsPrivateLocationFlyoutVisible } from '../../state/private_locations/actions';
|
import { selectAddingNewPrivateLocation } from '../../state/settings/selectors';
|
||||||
import { selectPrivateLocationFlyoutVisible } from '../../state/private_locations/selectors';
|
import { setIsCreatePrivateLocationFlyoutVisible } from '../../state/private_locations/actions';
|
||||||
|
|
||||||
export const GettingStartedPage = () => {
|
export const GettingStartedPage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -130,23 +127,23 @@ export const GettingStartedOnPrem = () => {
|
||||||
|
|
||||||
useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview
|
useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview
|
||||||
|
|
||||||
const isPrivateLocationFlyoutVisible = useSelector(selectPrivateLocationFlyoutVisible);
|
const isAddingNewLocation = useSelector(selectAddingNewPrivateLocation);
|
||||||
|
|
||||||
const setIsFlyoutOpen = useCallback(
|
const setIsAddingNewLocation = useCallback(
|
||||||
(val: boolean) => dispatch(setIsPrivateLocationFlyoutVisible(val)),
|
(val: boolean) => dispatch(setIsCreatePrivateLocationFlyoutVisible(val)),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { onCreateLocationAPI, privateLocations } = usePrivateLocationsAPI();
|
const { onSubmit, privateLocations } = usePrivateLocationsAPI();
|
||||||
|
|
||||||
const handleSubmit = (formData: NewLocation) => {
|
const handleSubmit = (formData: NewLocation) => {
|
||||||
onCreateLocationAPI(formData);
|
onSubmit(formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
// make sure flyout is closed when first visiting the page
|
// make sure flyout is closed when first visiting the page
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsFlyoutOpen(false);
|
setIsAddingNewLocation(false);
|
||||||
}, [setIsFlyoutOpen]);
|
}, [setIsAddingNewLocation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -166,7 +163,7 @@ export const GettingStartedOnPrem = () => {
|
||||||
fill
|
fill
|
||||||
iconType="plusInCircleFilled"
|
iconType="plusInCircleFilled"
|
||||||
data-test-subj="gettingStartedAddLocationButton"
|
data-test-subj="gettingStartedAddLocationButton"
|
||||||
onClick={() => setIsFlyoutOpen(true)}
|
onClick={() => setIsAddingNewLocation(true)}
|
||||||
>
|
>
|
||||||
{CREATE_LOCATION_LABEL}
|
{CREATE_LOCATION_LABEL}
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
|
@ -176,9 +173,9 @@ export const GettingStartedOnPrem = () => {
|
||||||
footer={<GettingStartedLink />}
|
footer={<GettingStartedLink />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isPrivateLocationFlyoutVisible ? (
|
{isAddingNewLocation ? (
|
||||||
<AddOrEditLocationFlyout
|
<AddLocationFlyout
|
||||||
onCloseFlyout={() => setIsFlyoutOpen(false)}
|
setIsOpen={setIsAddingNewLocation}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
privateLocations={privateLocations}
|
privateLocations={privateLocations}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,13 +16,9 @@ import { ClientPluginsStart } from '../../../../../plugin';
|
||||||
|
|
||||||
interface SpaceSelectorProps {
|
interface SpaceSelectorProps {
|
||||||
helpText: string;
|
helpText: string;
|
||||||
isDisabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpaceSelector = <T extends FieldValues>({
|
export const SpaceSelector = <T extends FieldValues>({ helpText }: SpaceSelectorProps) => {
|
||||||
helpText,
|
|
||||||
isDisabled = false,
|
|
||||||
}: SpaceSelectorProps) => {
|
|
||||||
const NAMESPACES_NAME = 'spaces' as Path<T>;
|
const NAMESPACES_NAME = 'spaces' as Path<T>;
|
||||||
const { services } = useKibana<ClientPluginsStart>();
|
const { services } = useKibana<ClientPluginsStart>();
|
||||||
const [spacesList, setSpacesList] = React.useState<Array<{ id: string; label: string }>>([]);
|
const [spacesList, setSpacesList] = React.useState<Array<{ id: string; label: string }>>([]);
|
||||||
|
@ -65,7 +61,6 @@ export const SpaceSelector = <T extends FieldValues>({
|
||||||
rules={{ required: true }}
|
rules={{ required: true }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<EuiComboBox
|
<EuiComboBox
|
||||||
isDisabled={isDisabled}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={SPACES_LABEL}
|
aria-label={SPACES_LABEL}
|
||||||
placeholder={SPACES_LABEL}
|
placeholder={SPACES_LABEL}
|
||||||
|
|
|
@ -15,12 +15,10 @@ export function TagsField({
|
||||||
tagsList,
|
tagsList,
|
||||||
control,
|
control,
|
||||||
errors,
|
errors,
|
||||||
isDisabled,
|
|
||||||
}: {
|
}: {
|
||||||
tagsList: string[];
|
tagsList: string[];
|
||||||
errors: FieldErrors;
|
errors: FieldErrors;
|
||||||
control: Control<PrivateLocation, any>;
|
control: Control<PrivateLocation, any>;
|
||||||
isDisabled?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<EuiFormRow fullWidth label={TAGS_LABEL}>
|
<EuiFormRow fullWidth label={TAGS_LABEL}>
|
||||||
|
@ -29,7 +27,6 @@ export function TagsField({
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<EuiComboBox
|
<EuiComboBox
|
||||||
isDisabled={isDisabled}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={TAGS_LABEL}
|
aria-label={TAGS_LABEL}
|
||||||
placeholder={TAGS_LABEL}
|
placeholder={TAGS_LABEL}
|
||||||
|
|
|
@ -35,22 +35,20 @@ import { selectPrivateLocationsState } from '../../../state/private_locations/se
|
||||||
export type NewLocation = Omit<PrivateLocation, 'id'>;
|
export type NewLocation = Omit<PrivateLocation, 'id'>;
|
||||||
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
||||||
|
|
||||||
export const AddOrEditLocationFlyout = ({
|
export const AddLocationFlyout = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCloseFlyout,
|
setIsOpen,
|
||||||
privateLocations,
|
privateLocations,
|
||||||
privateLocationToEdit,
|
|
||||||
}: {
|
}: {
|
||||||
onSubmit: (val: NewLocation) => void;
|
onSubmit: (val: NewLocation) => void;
|
||||||
onCloseFlyout: () => void;
|
setIsOpen: (val: boolean) => void;
|
||||||
privateLocations: PrivateLocation[];
|
privateLocations: PrivateLocation[];
|
||||||
privateLocationToEdit?: PrivateLocation;
|
|
||||||
}) => {
|
}) => {
|
||||||
const form = useFormWrapped({
|
const form = useFormWrapped({
|
||||||
mode: 'onSubmit',
|
mode: 'onSubmit',
|
||||||
reValidateMode: 'onChange',
|
reValidateMode: 'onChange',
|
||||||
shouldFocusError: true,
|
shouldFocusError: true,
|
||||||
defaultValues: privateLocationToEdit || {
|
defaultValues: {
|
||||||
label: '',
|
label: '',
|
||||||
agentPolicyId: '',
|
agentPolicyId: '',
|
||||||
geo: {
|
geo: {
|
||||||
|
@ -63,7 +61,7 @@ export const AddOrEditLocationFlyout = ({
|
||||||
|
|
||||||
const { canSave, canManagePrivateLocations } = useSyntheticsSettingsContext();
|
const { canSave, canManagePrivateLocations } = useSyntheticsSettingsContext();
|
||||||
|
|
||||||
const { createLoading, editLoading } = useSelector(selectPrivateLocationsState);
|
const { createLoading } = useSelector(selectPrivateLocationsState);
|
||||||
|
|
||||||
const { spaces: spacesApi } = useKibana<ClientPluginsStart>().services;
|
const { spaces: spacesApi } = useKibana<ClientPluginsStart>().services;
|
||||||
|
|
||||||
|
@ -74,35 +72,33 @@ export const AddOrEditLocationFlyout = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const { handleSubmit } = form;
|
const { handleSubmit } = form;
|
||||||
|
const closeFlyout = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<EuiFlyout onClose={onCloseFlyout} css={{ width: 540 }}>
|
<EuiFlyout onClose={closeFlyout} style={{ width: 540 }}>
|
||||||
<EuiFlyoutHeader hasBorder>
|
<EuiFlyoutHeader hasBorder>
|
||||||
<EuiTitle size="m">
|
<EuiTitle size="m">
|
||||||
<h2>
|
<h2>{ADD_PRIVATE_LOCATION}</h2>
|
||||||
{privateLocationToEdit !== undefined ? EDIT_PRIVATE_LOCATION : ADD_PRIVATE_LOCATION}
|
|
||||||
</h2>
|
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
</EuiFlyoutHeader>
|
</EuiFlyoutHeader>
|
||||||
<EuiFlyoutBody>
|
<EuiFlyoutBody>
|
||||||
<ManageEmptyState privateLocations={privateLocations} showEmptyLocations={false}>
|
<ManageEmptyState privateLocations={privateLocations} showEmptyLocations={false}>
|
||||||
<LocationForm
|
<LocationForm privateLocations={privateLocations} />
|
||||||
privateLocations={privateLocations}
|
|
||||||
privateLocationToEdit={privateLocationToEdit}
|
|
||||||
/>
|
|
||||||
</ManageEmptyState>
|
</ManageEmptyState>
|
||||||
</EuiFlyoutBody>
|
</EuiFlyoutBody>
|
||||||
<EuiFlyoutFooter>
|
<EuiFlyoutFooter>
|
||||||
<EuiFlexGroup justifyContent="spaceBetween">
|
<EuiFlexGroup justifyContent="spaceBetween">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButtonEmpty
|
<EuiButtonEmpty
|
||||||
data-test-subj="syntheticsLocationFlyoutCancelButton"
|
data-test-subj="syntheticsAddLocationFlyoutButton"
|
||||||
iconType="cross"
|
iconType="cross"
|
||||||
onClick={onCloseFlyout}
|
onClick={closeFlyout}
|
||||||
flush="left"
|
flush="left"
|
||||||
isLoading={createLoading || editLoading}
|
isLoading={createLoading}
|
||||||
>
|
>
|
||||||
{CANCEL_LABEL}
|
{CANCEL_LABEL}
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
|
@ -110,10 +106,10 @@ export const AddOrEditLocationFlyout = ({
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<NoPermissionsTooltip canEditSynthetics={canSave}>
|
<NoPermissionsTooltip canEditSynthetics={canSave}>
|
||||||
<EuiButton
|
<EuiButton
|
||||||
data-test-subj="syntheticsLocationFlyoutSaveButton"
|
data-test-subj="syntheticsAddLocationFlyoutButton"
|
||||||
fill
|
fill
|
||||||
onClick={handleSubmit(onSubmit)}
|
onClick={handleSubmit(onSubmit)}
|
||||||
isLoading={createLoading || editLoading}
|
isLoading={createLoading}
|
||||||
isDisabled={!canSave || !canManagePrivateLocations}
|
isDisabled={!canSave || !canManagePrivateLocations}
|
||||||
>
|
>
|
||||||
{SAVE_LABEL}
|
{SAVE_LABEL}
|
||||||
|
@ -135,13 +131,6 @@ const ADD_PRIVATE_LOCATION = i18n.translate(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const EDIT_PRIVATE_LOCATION = i18n.translate(
|
|
||||||
'xpack.synthetics.monitorManagement.editPrivateLocations',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Edit private location',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const CANCEL_LABEL = i18n.translate('xpack.synthetics.monitorManagement.cancelLabel', {
|
const CANCEL_LABEL = i18n.translate('xpack.synthetics.monitorManagement.cancelLabel', {
|
||||||
defaultMessage: 'Cancel',
|
defaultMessage: 'Cancel',
|
||||||
});
|
});
|
|
@ -12,19 +12,19 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { NoPermissionsTooltip } from '../../common/components/permissions';
|
import { NoPermissionsTooltip } from '../../common/components/permissions';
|
||||||
import { useSyntheticsSettingsContext } from '../../../contexts';
|
import { useSyntheticsSettingsContext } from '../../../contexts';
|
||||||
import { PRIVATE_LOCATIONS_ROUTE } from '../../../../../../common/constants';
|
import { PRIVATE_LOCATIOSN_ROUTE } from '../../../../../../common/constants';
|
||||||
import {
|
import {
|
||||||
setIsPrivateLocationFlyoutVisible,
|
setIsCreatePrivateLocationFlyoutVisible,
|
||||||
setManageFlyoutOpen,
|
setManageFlyoutOpen,
|
||||||
} from '../../../state/private_locations/actions';
|
} from '../../../state/private_locations/actions';
|
||||||
|
|
||||||
export const EmptyLocations = ({
|
export const EmptyLocations = ({
|
||||||
inFlyout = true,
|
inFlyout = true,
|
||||||
setIsFlyoutOpen,
|
setIsAddingNew,
|
||||||
redirectToSettings,
|
redirectToSettings,
|
||||||
}: {
|
}: {
|
||||||
inFlyout?: boolean;
|
inFlyout?: boolean;
|
||||||
setIsFlyoutOpen?: (val: boolean) => void;
|
setIsAddingNew?: (val: boolean) => void;
|
||||||
redirectToSettings?: boolean;
|
redirectToSettings?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -52,7 +52,7 @@ export const EmptyLocations = ({
|
||||||
fill
|
fill
|
||||||
isDisabled={!canSave}
|
isDisabled={!canSave}
|
||||||
href={history.createHref({
|
href={history.createHref({
|
||||||
pathname: PRIVATE_LOCATIONS_ROUTE,
|
pathname: PRIVATE_LOCATIOSN_ROUTE,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{ADD_LOCATION}
|
{ADD_LOCATION}
|
||||||
|
@ -65,9 +65,9 @@ export const EmptyLocations = ({
|
||||||
color="primary"
|
color="primary"
|
||||||
fill
|
fill
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsFlyoutOpen?.(true);
|
setIsAddingNew?.(true);
|
||||||
dispatch(setManageFlyoutOpen(true));
|
dispatch(setManageFlyoutOpen(true));
|
||||||
dispatch(setIsPrivateLocationFlyoutVisible(true));
|
dispatch(setIsCreatePrivateLocationFlyoutVisible(true));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ADD_LOCATION}
|
{ADD_LOCATION}
|
||||||
|
|
|
@ -7,12 +7,10 @@
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import type { EditPrivateLocationAttributes } from '../../../../../../../server/routes/settings/private_locations/edit_private_location';
|
import { NewLocation } from '../add_location_flyout';
|
||||||
import { NewLocation } from '../add_or_edit_location_flyout';
|
|
||||||
import {
|
import {
|
||||||
createPrivateLocationAction,
|
createPrivateLocationAction,
|
||||||
deletePrivateLocationAction,
|
deletePrivateLocationAction,
|
||||||
editPrivateLocationAction,
|
|
||||||
getPrivateLocationsAction,
|
getPrivateLocationsAction,
|
||||||
} from '../../../../state/private_locations/actions';
|
} from '../../../../state/private_locations/actions';
|
||||||
import { selectPrivateLocationsState } from '../../../../state/private_locations/selectors';
|
import { selectPrivateLocationsState } from '../../../../state/private_locations/selectors';
|
||||||
|
@ -32,22 +30,17 @@ export const usePrivateLocationsAPI = () => {
|
||||||
}
|
}
|
||||||
}, [data, dispatch]);
|
}, [data, dispatch]);
|
||||||
|
|
||||||
const onCreateLocationAPI = (newLoc: NewLocation) => {
|
const onSubmit = (newLoc: NewLocation) => {
|
||||||
dispatch(createPrivateLocationAction.get(newLoc));
|
dispatch(createPrivateLocationAction.get(newLoc));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEditLocationAPI = (locationId: string, newAttributes: EditPrivateLocationAttributes) => {
|
const onDelete = (id: string) => {
|
||||||
dispatch(editPrivateLocationAction.get({ locationId, newAttributes }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDeleteLocationAPI = (id: string) => {
|
|
||||||
dispatch(deletePrivateLocationAction.get(id));
|
dispatch(deletePrivateLocationAction.get(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onCreateLocationAPI,
|
onSubmit,
|
||||||
onEditLocationAPI,
|
onDelete,
|
||||||
onDeleteLocationAPI,
|
|
||||||
deleteLoading,
|
deleteLoading,
|
||||||
loading,
|
loading,
|
||||||
createLoading,
|
createLoading,
|
||||||
|
|
|
@ -18,13 +18,7 @@ import { PrivateLocation } from '../../../../../../common/runtime_types';
|
||||||
import { AgentPolicyNeeded } from './agent_policy_needed';
|
import { AgentPolicyNeeded } from './agent_policy_needed';
|
||||||
import { PolicyHostsField } from './policy_hosts';
|
import { PolicyHostsField } from './policy_hosts';
|
||||||
|
|
||||||
export const LocationForm = ({
|
export const LocationForm = ({ privateLocations }: { privateLocations: PrivateLocation[] }) => {
|
||||||
privateLocations,
|
|
||||||
privateLocationToEdit,
|
|
||||||
}: {
|
|
||||||
privateLocations: PrivateLocation[];
|
|
||||||
privateLocationToEdit?: PrivateLocation;
|
|
||||||
}) => {
|
|
||||||
const { data } = useSelector(selectAgentPolicies);
|
const { data } = useSelector(selectAgentPolicies);
|
||||||
const { control, register } = useFormContext<PrivateLocation>();
|
const { control, register } = useFormContext<PrivateLocation>();
|
||||||
const { errors } = useFormState();
|
const { errors } = useFormState();
|
||||||
|
@ -34,8 +28,6 @@ export const LocationForm = ({
|
||||||
return [...acc, ...tags];
|
return [...acc, ...tags];
|
||||||
}, [] as string[]);
|
}, [] as string[]);
|
||||||
|
|
||||||
const isEditingLocation = privateLocationToEdit !== undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data?.length === 0 && <AgentPolicyNeeded />}
|
{data?.length === 0 && <AgentPolicyNeeded />}
|
||||||
|
@ -56,8 +48,7 @@ export const LocationForm = ({
|
||||||
message: NAME_REQUIRED,
|
message: NAME_REQUIRED,
|
||||||
},
|
},
|
||||||
validate: (val: string) => {
|
validate: (val: string) => {
|
||||||
return privateLocations.some((loc) => loc.label === val) &&
|
return privateLocations.some((loc) => loc.label === val)
|
||||||
val !== privateLocationToEdit?.label
|
|
||||||
? NAME_ALREADY_EXISTS
|
? NAME_ALREADY_EXISTS
|
||||||
: undefined;
|
: undefined;
|
||||||
},
|
},
|
||||||
|
@ -65,13 +56,13 @@ export const LocationForm = ({
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
<PolicyHostsField privateLocations={privateLocations} isDisabled={isEditingLocation} />
|
<PolicyHostsField privateLocations={privateLocations} />
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
<TagsField tagsList={tagsList} control={control} errors={errors} />
|
<TagsField tagsList={tagsList} control={control} errors={errors} />
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
<BrowserMonitorCallout />
|
<BrowserMonitorCallout />
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
<SpaceSelector helpText={LOCATION_HELP_TEXT} isDisabled={isEditingLocation} />
|
<SpaceSelector helpText={LOCATION_HELP_TEXT} />
|
||||||
</EuiForm>
|
</EuiForm>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
EuiBadge,
|
EuiBadge,
|
||||||
EuiBasicTableColumn,
|
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
|
@ -32,7 +31,7 @@ import { DeleteLocation } from './delete_location';
|
||||||
import { useLocationMonitors } from './hooks/use_location_monitors';
|
import { useLocationMonitors } from './hooks/use_location_monitors';
|
||||||
import { PolicyName } from './policy_name';
|
import { PolicyName } from './policy_name';
|
||||||
import { LOCATION_NAME_LABEL } from './location_form';
|
import { LOCATION_NAME_LABEL } from './location_form';
|
||||||
import { setIsPrivateLocationFlyoutVisible } from '../../../state/private_locations/actions';
|
import { setIsCreatePrivateLocationFlyoutVisible } from '../../../state/private_locations/actions';
|
||||||
import { ClientPluginsStart } from '../../../../../plugin';
|
import { ClientPluginsStart } from '../../../../../plugin';
|
||||||
|
|
||||||
interface ListItem extends PrivateLocation {
|
interface ListItem extends PrivateLocation {
|
||||||
|
@ -42,12 +41,10 @@ interface ListItem extends PrivateLocation {
|
||||||
export const PrivateLocationsTable = ({
|
export const PrivateLocationsTable = ({
|
||||||
deleteLoading,
|
deleteLoading,
|
||||||
onDelete,
|
onDelete,
|
||||||
onEdit,
|
|
||||||
privateLocations,
|
privateLocations,
|
||||||
}: {
|
}: {
|
||||||
deleteLoading?: boolean;
|
deleteLoading?: boolean;
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
onEdit: (privateLocation: PrivateLocation) => void;
|
|
||||||
privateLocations: PrivateLocation[];
|
privateLocations: PrivateLocation[];
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -68,7 +65,7 @@ export const PrivateLocationsTable = ({
|
||||||
return new Set([...acc, ...tags]);
|
return new Set([...acc, ...tags]);
|
||||||
}, new Set<string>());
|
}, new Set<string>());
|
||||||
|
|
||||||
const columns: Array<EuiBasicTableColumn<ListItem>> = [
|
const columns = [
|
||||||
{
|
{
|
||||||
field: 'label',
|
field: 'label',
|
||||||
name: LOCATION_NAME_LABEL,
|
name: LOCATION_NAME_LABEL,
|
||||||
|
@ -117,15 +114,6 @@ export const PrivateLocationsTable = ({
|
||||||
{
|
{
|
||||||
name: ACTIONS_LABEL,
|
name: ACTIONS_LABEL,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
|
||||||
name: EDIT_LOCATION,
|
|
||||||
description: EDIT_LOCATION,
|
|
||||||
isPrimary: true,
|
|
||||||
'data-test-subj': 'action-edit',
|
|
||||||
onClick: onEdit,
|
|
||||||
icon: 'pencil',
|
|
||||||
type: 'icon',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: DELETE_LOCATION,
|
name: DELETE_LOCATION,
|
||||||
description: DELETE_LOCATION,
|
description: DELETE_LOCATION,
|
||||||
|
@ -150,7 +138,7 @@ export const PrivateLocationsTable = ({
|
||||||
monitors: locationMonitors?.find((l) => l.id === location.id)?.count ?? 0,
|
monitors: locationMonitors?.find((l) => l.id === location.id)?.count ?? 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const openFlyout = () => dispatch(setIsPrivateLocationFlyoutVisible(true));
|
const setIsAddingNew = (val: boolean) => dispatch(setIsCreatePrivateLocationFlyoutVisible(val));
|
||||||
|
|
||||||
const renderToolRight = () => {
|
const renderToolRight = () => {
|
||||||
return [
|
return [
|
||||||
|
@ -164,7 +152,7 @@ export const PrivateLocationsTable = ({
|
||||||
data-test-subj={'addPrivateLocationButton'}
|
data-test-subj={'addPrivateLocationButton'}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
disabled={!canSave || !canManagePrivateLocations}
|
disabled={!canSave || !canManagePrivateLocations}
|
||||||
onClick={openFlyout}
|
onClick={() => setIsAddingNew(true)}
|
||||||
iconType="plusInCircle"
|
iconType="plusInCircle"
|
||||||
>
|
>
|
||||||
{ADD_LABEL}
|
{ADD_LABEL}
|
||||||
|
@ -248,10 +236,6 @@ const DELETE_LOCATION = i18n.translate(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const EDIT_LOCATION = i18n.translate('xpack.synthetics.settingsRoute.privateLocations.editLabel', {
|
|
||||||
defaultMessage: 'Edit private location',
|
|
||||||
});
|
|
||||||
|
|
||||||
const ADD_LABEL = i18n.translate('xpack.synthetics.monitorManagement.createLocation', {
|
const ADD_LABEL = i18n.translate('xpack.synthetics.monitorManagement.createLocation', {
|
||||||
defaultMessage: 'Create location',
|
defaultMessage: 'Create location',
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,14 +15,14 @@ import { selectAgentPolicies } from '../../../state/agent_policies';
|
||||||
export const ManageEmptyState: FC<
|
export const ManageEmptyState: FC<
|
||||||
PropsWithChildren<{
|
PropsWithChildren<{
|
||||||
privateLocations: PrivateLocation[];
|
privateLocations: PrivateLocation[];
|
||||||
setIsFlyoutOpen?: (val: boolean) => void;
|
setIsAddingNew?: (val: boolean) => void;
|
||||||
showNeedAgentPolicy?: boolean;
|
showNeedAgentPolicy?: boolean;
|
||||||
showEmptyLocations?: boolean;
|
showEmptyLocations?: boolean;
|
||||||
}>
|
}>
|
||||||
> = ({
|
> = ({
|
||||||
children,
|
children,
|
||||||
privateLocations,
|
privateLocations,
|
||||||
setIsFlyoutOpen,
|
setIsAddingNew,
|
||||||
showNeedAgentPolicy = true,
|
showNeedAgentPolicy = true,
|
||||||
showEmptyLocations = true,
|
showEmptyLocations = true,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -33,7 +33,7 @@ export const ManageEmptyState: FC<
|
||||||
}
|
}
|
||||||
|
|
||||||
if (privateLocations.length === 0 && showEmptyLocations) {
|
if (privateLocations.length === 0 && showEmptyLocations) {
|
||||||
return <EmptyLocations setIsFlyoutOpen={setIsFlyoutOpen} />;
|
return <EmptyLocations setIsAddingNew={setIsAddingNew} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
|
|
@ -28,10 +28,9 @@ describe('<ManagePrivateLocations />', () => {
|
||||||
});
|
});
|
||||||
jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({
|
jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({
|
||||||
loading: false,
|
loading: false,
|
||||||
onCreateLocationAPI: jest.fn(),
|
onSubmit: jest.fn(),
|
||||||
onEditLocationAPI: jest.fn(),
|
|
||||||
privateLocations: [],
|
privateLocations: [],
|
||||||
onDeleteLocationAPI: jest.fn(),
|
onDelete: jest.fn(),
|
||||||
deleteLoading: false,
|
deleteLoading: false,
|
||||||
createLoading: false,
|
createLoading: false,
|
||||||
});
|
});
|
||||||
|
@ -61,7 +60,7 @@ describe('<ManagePrivateLocations />', () => {
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: false,
|
isCreatePrivateLocationFlyoutVisible: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -96,7 +95,7 @@ describe('<ManagePrivateLocations />', () => {
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: false,
|
isCreatePrivateLocationFlyoutVisible: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -126,8 +125,7 @@ describe('<ManagePrivateLocations />', () => {
|
||||||
|
|
||||||
jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({
|
jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({
|
||||||
loading: false,
|
loading: false,
|
||||||
onCreateLocationAPI: jest.fn(),
|
onSubmit: jest.fn(),
|
||||||
onEditLocationAPI: jest.fn(),
|
|
||||||
privateLocations: [
|
privateLocations: [
|
||||||
{
|
{
|
||||||
label: privateLocationName,
|
label: privateLocationName,
|
||||||
|
@ -136,7 +134,7 @@ describe('<ManagePrivateLocations />', () => {
|
||||||
isServiceManaged: false,
|
isServiceManaged: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onDeleteLocationAPI: jest.fn(),
|
onDelete: jest.fn(),
|
||||||
deleteLoading: false,
|
deleteLoading: false,
|
||||||
createLoading: false,
|
createLoading: false,
|
||||||
});
|
});
|
||||||
|
@ -148,7 +146,7 @@ describe('<ManagePrivateLocations />', () => {
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: false,
|
isCreatePrivateLocationFlyoutVisible: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,23 +8,15 @@ import React, { useEffect, useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||||
import { SpacesContextProps } from '@kbn/spaces-plugin/public';
|
import { SpacesContextProps } from '@kbn/spaces-plugin/public';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
import { PrivateLocation } from '../../../../../../common/runtime_types';
|
|
||||||
import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout';
|
import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout';
|
||||||
import { PrivateLocationsTable } from './locations_table';
|
import { PrivateLocationsTable } from './locations_table';
|
||||||
import { ManageEmptyState } from './manage_empty_state';
|
import { ManageEmptyState } from './manage_empty_state';
|
||||||
import { AddOrEditLocationFlyout, NewLocation } from './add_or_edit_location_flyout';
|
import { AddLocationFlyout, NewLocation } from './add_location_flyout';
|
||||||
import { usePrivateLocationsAPI } from './hooks/use_locations_api';
|
import { usePrivateLocationsAPI } from './hooks/use_locations_api';
|
||||||
import {
|
import { selectAddingNewPrivateLocation } from '../../../state/private_locations/selectors';
|
||||||
selectPrivateLocationFlyoutVisible,
|
|
||||||
selectPrivateLocationToEdit,
|
|
||||||
} from '../../../state/private_locations/selectors';
|
|
||||||
import { getServiceLocations } from '../../../state';
|
import { getServiceLocations } from '../../../state';
|
||||||
import { getAgentPoliciesAction } from '../../../state/agent_policies';
|
import { getAgentPoliciesAction } from '../../../state/agent_policies';
|
||||||
import {
|
import { setIsCreatePrivateLocationFlyoutVisible } from '../../../state/private_locations/actions';
|
||||||
setIsPrivateLocationFlyoutVisible as setIsPrivateLocationFlyoutVisible,
|
|
||||||
setPrivateLocationToEdit,
|
|
||||||
} from '../../../state/private_locations/actions';
|
|
||||||
import { ClientPluginsStart } from '../../../../../plugin';
|
import { ClientPluginsStart } from '../../../../../plugin';
|
||||||
|
|
||||||
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
||||||
|
@ -41,53 +33,23 @@ export const ManagePrivateLocations = () => {
|
||||||
[spacesApi]
|
[spacesApi]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isPrivateLocationFlyoutVisible = useSelector(selectPrivateLocationFlyoutVisible);
|
const isAddingNew = useSelector(selectAddingNewPrivateLocation);
|
||||||
const privateLocationToEdit = useSelector(selectPrivateLocationToEdit);
|
const setIsAddingNew = useCallback(
|
||||||
const setIsFlyoutOpen = useCallback(
|
(val: boolean) => dispatch(setIsCreatePrivateLocationFlyoutVisible(val)),
|
||||||
(val: boolean) => dispatch(setIsPrivateLocationFlyoutVisible(val)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { onSubmit, loading, privateLocations, onDelete, deleteLoading } = usePrivateLocationsAPI();
|
||||||
onCreateLocationAPI,
|
|
||||||
onEditLocationAPI,
|
|
||||||
loading,
|
|
||||||
privateLocations,
|
|
||||||
onDeleteLocationAPI,
|
|
||||||
deleteLoading,
|
|
||||||
} = usePrivateLocationsAPI();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getAgentPoliciesAction.get());
|
dispatch(getAgentPoliciesAction.get());
|
||||||
dispatch(getServiceLocations());
|
dispatch(getServiceLocations());
|
||||||
// make sure flyout is closed when first visiting the page
|
// make sure flyout is closed when first visiting the page
|
||||||
dispatch(setIsPrivateLocationFlyoutVisible(false));
|
dispatch(setIsCreatePrivateLocationFlyoutVisible(false));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleSubmit = (formData: NewLocation) => {
|
const handleSubmit = (formData: NewLocation) => {
|
||||||
if (privateLocationToEdit) {
|
onSubmit(formData);
|
||||||
const isLabelChanged = formData.label !== privateLocationToEdit.label;
|
|
||||||
const areTagsChanged = !isEqual(formData.tags, privateLocationToEdit.tags);
|
|
||||||
if (!isLabelChanged && !areTagsChanged) {
|
|
||||||
onCloseFlyout();
|
|
||||||
} else {
|
|
||||||
onEditLocationAPI(privateLocationToEdit.id, { label: formData.label, tags: formData.tags });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onCreateLocationAPI(formData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEditLocation = (privateLocation: PrivateLocation) => {
|
|
||||||
dispatch(setPrivateLocationToEdit(privateLocation));
|
|
||||||
setIsFlyoutOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCloseFlyout = () => {
|
|
||||||
if (privateLocationToEdit) {
|
|
||||||
dispatch(setPrivateLocationToEdit(undefined));
|
|
||||||
}
|
|
||||||
setIsFlyoutOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -95,22 +57,20 @@ export const ManagePrivateLocations = () => {
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingState />
|
<LoadingState />
|
||||||
) : (
|
) : (
|
||||||
<ManageEmptyState privateLocations={privateLocations} setIsFlyoutOpen={setIsFlyoutOpen}>
|
<ManageEmptyState privateLocations={privateLocations} setIsAddingNew={setIsAddingNew}>
|
||||||
<PrivateLocationsTable
|
<PrivateLocationsTable
|
||||||
privateLocations={privateLocations}
|
privateLocations={privateLocations}
|
||||||
onDelete={onDeleteLocationAPI}
|
onDelete={onDelete}
|
||||||
onEdit={onEditLocation}
|
|
||||||
deleteLoading={deleteLoading}
|
deleteLoading={deleteLoading}
|
||||||
/>
|
/>
|
||||||
</ManageEmptyState>
|
</ManageEmptyState>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isPrivateLocationFlyoutVisible ? (
|
{isAddingNew ? (
|
||||||
<AddOrEditLocationFlyout
|
<AddLocationFlyout
|
||||||
onCloseFlyout={onCloseFlyout}
|
setIsOpen={setIsAddingNew}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
privateLocations={privateLocations}
|
privateLocations={privateLocations}
|
||||||
privateLocationToEdit={privateLocationToEdit}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</SpacesContextProvider>
|
</SpacesContextProvider>
|
||||||
|
|
|
@ -29,13 +29,7 @@ import { selectAgentPolicies } from '../../../state/agent_policies';
|
||||||
|
|
||||||
export const AGENT_POLICY_FIELD_NAME = 'agentPolicyId';
|
export const AGENT_POLICY_FIELD_NAME = 'agentPolicyId';
|
||||||
|
|
||||||
export const PolicyHostsField = ({
|
export const PolicyHostsField = ({ privateLocations }: { privateLocations: PrivateLocation[] }) => {
|
||||||
privateLocations,
|
|
||||||
isDisabled,
|
|
||||||
}: {
|
|
||||||
privateLocations: PrivateLocation[];
|
|
||||||
isDisabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { data } = useSelector(selectAgentPolicies);
|
const { data } = useSelector(selectAgentPolicies);
|
||||||
const { basePath } = useSyntheticsSettingsContext();
|
const { basePath } = useSyntheticsSettingsContext();
|
||||||
|
|
||||||
|
@ -59,7 +53,7 @@ export const PolicyHostsField = ({
|
||||||
inputDisplay: (
|
inputDisplay: (
|
||||||
<EuiHealth
|
<EuiHealth
|
||||||
color={item.status === 'active' ? 'success' : 'warning'}
|
color={item.status === 'active' ? 'success' : 'warning'}
|
||||||
css={{ lineHeight: 'inherit' }}
|
style={{ lineHeight: 'inherit' }}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</EuiHealth>
|
</EuiHealth>
|
||||||
|
@ -80,7 +74,7 @@ export const PolicyHostsField = ({
|
||||||
<>
|
<>
|
||||||
<EuiHealth
|
<EuiHealth
|
||||||
color={item.status === 'active' ? 'success' : 'warning'}
|
color={item.status === 'active' ? 'success' : 'warning'}
|
||||||
css={{ lineHeight: 'inherit' }}
|
style={{ lineHeight: 'inherit' }}
|
||||||
>
|
>
|
||||||
<strong>{item.name}</strong>
|
<strong>{item.name}</strong>
|
||||||
</EuiHealth>
|
</EuiHealth>
|
||||||
|
@ -130,7 +124,6 @@ export const PolicyHostsField = ({
|
||||||
rules={{ required: true }}
|
rules={{ required: true }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<SuperSelect
|
<SuperSelect
|
||||||
disabled={isDisabled}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={SELECT_POLICY_HOSTS}
|
aria-label={SELECT_POLICY_HOSTS}
|
||||||
placeholder={SELECT_POLICY_HOSTS}
|
placeholder={SELECT_POLICY_HOSTS}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NewLocation } from '../../components/settings/private_locations/add_or_edit_location_flyout';
|
import { NewLocation } from '../../components/settings/private_locations/add_location_flyout';
|
||||||
import { AgentPolicyInfo } from '../../../../../common/types';
|
import { AgentPolicyInfo } from '../../../../../common/types';
|
||||||
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
||||||
import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { NewLocation } from '../../components/settings/private_locations/add_or_edit_location_flyout';
|
import { NewLocation } from '../../components/settings/private_locations/add_location_flyout';
|
||||||
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
||||||
import { createAsyncAction } from '../utils/actions';
|
import { createAsyncAction } from '../utils/actions';
|
||||||
import type { EditPrivateLocationAttributes } from '../../../../../server/routes/settings/private_locations/edit_private_location';
|
|
||||||
|
|
||||||
export const getPrivateLocationsAction = createAsyncAction<void, SyntheticsPrivateLocations>(
|
export const getPrivateLocationsAction = createAsyncAction<void, SyntheticsPrivateLocations>(
|
||||||
'[PRIVATE LOCATIONS] GET'
|
'[PRIVATE LOCATIONS] GET'
|
||||||
|
@ -19,24 +18,12 @@ export const createPrivateLocationAction = createAsyncAction<NewLocation, Privat
|
||||||
'CREATE PRIVATE LOCATION'
|
'CREATE PRIVATE LOCATION'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const editPrivateLocationAction = createAsyncAction<
|
|
||||||
{
|
|
||||||
locationId: string;
|
|
||||||
newAttributes: EditPrivateLocationAttributes;
|
|
||||||
},
|
|
||||||
PrivateLocation
|
|
||||||
>('EDIT PRIVATE LOCATION');
|
|
||||||
|
|
||||||
export const deletePrivateLocationAction = createAsyncAction<string, SyntheticsPrivateLocations>(
|
export const deletePrivateLocationAction = createAsyncAction<string, SyntheticsPrivateLocations>(
|
||||||
'DELETE PRIVATE LOCATION'
|
'DELETE PRIVATE LOCATION'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const setManageFlyoutOpen = createAction<boolean>('SET MANAGE FLYOUT OPEN');
|
export const setManageFlyoutOpen = createAction<boolean>('SET MANAGE FLYOUT OPEN');
|
||||||
|
|
||||||
export const setIsPrivateLocationFlyoutVisible = createAction<boolean>(
|
export const setIsCreatePrivateLocationFlyoutVisible = createAction<boolean>(
|
||||||
'SET IS CREATE OR EDIT PRIVATE LOCATION FLYOUT VISIBLE'
|
'SET IS CREATE PRIVATE LOCATION FLYOUT VISIBLE'
|
||||||
);
|
|
||||||
|
|
||||||
export const setPrivateLocationToEdit = createAction<PrivateLocation | undefined>(
|
|
||||||
'SET PRIVATE LOCATION TO EDIT'
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { EditPrivateLocationAttributes } from '../../../../../server/routes/settings/private_locations/edit_private_location';
|
import { NewLocation } from '../../components/settings/private_locations/add_location_flyout';
|
||||||
import { NewLocation } from '../../components/settings/private_locations/add_or_edit_location_flyout';
|
|
||||||
import { AgentPolicyInfo } from '../../../../../common/types';
|
import { AgentPolicyInfo } from '../../../../../common/types';
|
||||||
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
||||||
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
||||||
|
@ -24,23 +23,6 @@ export const createSyntheticsPrivateLocation = async (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editSyntheticsPrivateLocation = async ({
|
|
||||||
locationId,
|
|
||||||
newAttributes,
|
|
||||||
}: {
|
|
||||||
locationId: string;
|
|
||||||
newAttributes: EditPrivateLocationAttributes;
|
|
||||||
}): Promise<PrivateLocation> => {
|
|
||||||
return apiService.put(
|
|
||||||
`${SYNTHETICS_API_URLS.PRIVATE_LOCATIONS}/${locationId}`,
|
|
||||||
newAttributes,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
version: INITIAL_REST_VERSION,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSyntheticsPrivateLocations = async (): Promise<SyntheticsPrivateLocations> => {
|
export const getSyntheticsPrivateLocations = async (): Promise<SyntheticsPrivateLocations> => {
|
||||||
return await apiService.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, {
|
return await apiService.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, {
|
||||||
version: INITIAL_REST_VERSION,
|
version: INITIAL_REST_VERSION,
|
||||||
|
|
|
@ -11,13 +11,11 @@ import { fetchEffectFactory } from '../utils/fetch_effect';
|
||||||
import {
|
import {
|
||||||
createSyntheticsPrivateLocation,
|
createSyntheticsPrivateLocation,
|
||||||
deleteSyntheticsPrivateLocation,
|
deleteSyntheticsPrivateLocation,
|
||||||
editSyntheticsPrivateLocation,
|
|
||||||
getSyntheticsPrivateLocations,
|
getSyntheticsPrivateLocations,
|
||||||
} from './api';
|
} from './api';
|
||||||
import {
|
import {
|
||||||
createPrivateLocationAction,
|
createPrivateLocationAction,
|
||||||
deletePrivateLocationAction,
|
deletePrivateLocationAction,
|
||||||
editPrivateLocationAction,
|
|
||||||
getPrivateLocationsAction,
|
getPrivateLocationsAction,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
|
||||||
|
@ -49,23 +47,6 @@ export function* createPrivateLocationEffect() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* editPrivateLocationEffect() {
|
|
||||||
yield takeLeading(
|
|
||||||
editPrivateLocationAction.get,
|
|
||||||
fetchEffectFactory(
|
|
||||||
editSyntheticsPrivateLocation,
|
|
||||||
editPrivateLocationAction.success,
|
|
||||||
editPrivateLocationAction.fail,
|
|
||||||
i18n.translate('xpack.synthetics.editPrivateLocationSuccess', {
|
|
||||||
defaultMessage: 'Successfully edited private location.',
|
|
||||||
}),
|
|
||||||
i18n.translate('xpack.synthetics.editPrivateLocationFailure', {
|
|
||||||
defaultMessage: 'Failed to edit private location.',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* deletePrivateLocationEffect() {
|
export function* deletePrivateLocationEffect() {
|
||||||
yield takeLeading(
|
yield takeLeading(
|
||||||
deletePrivateLocationAction.get,
|
deletePrivateLocationAction.get,
|
||||||
|
@ -80,6 +61,5 @@ export function* deletePrivateLocationEffect() {
|
||||||
export const privateLocationsEffects = [
|
export const privateLocationsEffects = [
|
||||||
fetchPrivateLocationsEffect,
|
fetchPrivateLocationsEffect,
|
||||||
createPrivateLocationEffect,
|
createPrivateLocationEffect,
|
||||||
editPrivateLocationEffect,
|
|
||||||
deletePrivateLocationEffect,
|
deletePrivateLocationEffect,
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,26 +7,19 @@
|
||||||
|
|
||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
|
||||||
import {
|
import { createPrivateLocationAction, deletePrivateLocationAction } from './actions';
|
||||||
createPrivateLocationAction,
|
import { setIsCreatePrivateLocationFlyoutVisible, getPrivateLocationsAction } from './actions';
|
||||||
deletePrivateLocationAction,
|
|
||||||
editPrivateLocationAction,
|
|
||||||
setPrivateLocationToEdit,
|
|
||||||
} from './actions';
|
|
||||||
import { setIsPrivateLocationFlyoutVisible, getPrivateLocationsAction } from './actions';
|
|
||||||
import { IHttpSerializedFetchError } from '../utils/http_error';
|
import { IHttpSerializedFetchError } from '../utils/http_error';
|
||||||
|
|
||||||
export interface PrivateLocationsState {
|
export interface PrivateLocationsState {
|
||||||
data?: SyntheticsPrivateLocations | null;
|
data?: SyntheticsPrivateLocations | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
createLoading?: boolean;
|
createLoading?: boolean;
|
||||||
editLoading?: boolean;
|
|
||||||
deleteLoading?: boolean;
|
deleteLoading?: boolean;
|
||||||
error: IHttpSerializedFetchError | null;
|
error: IHttpSerializedFetchError | null;
|
||||||
isManageFlyoutOpen?: boolean;
|
isManageFlyoutOpen?: boolean;
|
||||||
isPrivateLocationFlyoutVisible?: boolean;
|
isCreatePrivateLocationFlyoutVisible?: boolean;
|
||||||
newLocation?: PrivateLocation;
|
newLocation?: PrivateLocation;
|
||||||
privateLocationToEdit?: PrivateLocation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: PrivateLocationsState = {
|
const initialState: PrivateLocationsState = {
|
||||||
|
@ -34,10 +27,8 @@ const initialState: PrivateLocationsState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
isManageFlyoutOpen: false,
|
isManageFlyoutOpen: false,
|
||||||
isPrivateLocationFlyoutVisible: false,
|
isCreatePrivateLocationFlyoutVisible: false,
|
||||||
createLoading: false,
|
createLoading: false,
|
||||||
editLoading: false,
|
|
||||||
privateLocationToEdit: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const privateLocationsStateReducer = createReducer(initialState, (builder) => {
|
export const privateLocationsStateReducer = createReducer(initialState, (builder) => {
|
||||||
|
@ -60,26 +51,12 @@ export const privateLocationsStateReducer = createReducer(initialState, (builder
|
||||||
state.newLocation = action.payload;
|
state.newLocation = action.payload;
|
||||||
state.createLoading = false;
|
state.createLoading = false;
|
||||||
state.data = null;
|
state.data = null;
|
||||||
state.isPrivateLocationFlyoutVisible = false;
|
state.isCreatePrivateLocationFlyoutVisible = false;
|
||||||
})
|
})
|
||||||
.addCase(createPrivateLocationAction.fail, (state, action) => {
|
.addCase(createPrivateLocationAction.fail, (state, action) => {
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
state.createLoading = false;
|
state.createLoading = false;
|
||||||
})
|
})
|
||||||
.addCase(editPrivateLocationAction.get, (state) => {
|
|
||||||
state.editLoading = true;
|
|
||||||
})
|
|
||||||
.addCase(editPrivateLocationAction.success, (state, action) => {
|
|
||||||
state.editLoading = false;
|
|
||||||
state.privateLocationToEdit = undefined;
|
|
||||||
state.data = null;
|
|
||||||
state.isPrivateLocationFlyoutVisible = false;
|
|
||||||
})
|
|
||||||
.addCase(editPrivateLocationAction.fail, (state, action) => {
|
|
||||||
state.editLoading = false;
|
|
||||||
state.privateLocationToEdit = undefined;
|
|
||||||
state.error = action.payload;
|
|
||||||
})
|
|
||||||
.addCase(deletePrivateLocationAction.get, (state) => {
|
.addCase(deletePrivateLocationAction.get, (state) => {
|
||||||
state.deleteLoading = true;
|
state.deleteLoading = true;
|
||||||
})
|
})
|
||||||
|
@ -91,10 +68,7 @@ export const privateLocationsStateReducer = createReducer(initialState, (builder
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
state.deleteLoading = false;
|
state.deleteLoading = false;
|
||||||
})
|
})
|
||||||
.addCase(setIsPrivateLocationFlyoutVisible, (state, action) => {
|
.addCase(setIsCreatePrivateLocationFlyoutVisible, (state, action) => {
|
||||||
state.isPrivateLocationFlyoutVisible = action.payload;
|
state.isCreatePrivateLocationFlyoutVisible = action.payload;
|
||||||
})
|
|
||||||
.addCase(setPrivateLocationToEdit, (state, action) => {
|
|
||||||
state.privateLocationToEdit = action.payload;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,11 +11,8 @@ import { AppState } from '..';
|
||||||
const getState = (appState: AppState) => appState.privateLocations;
|
const getState = (appState: AppState) => appState.privateLocations;
|
||||||
export const selectAgentPolicies = createSelector(getState, (state) => state);
|
export const selectAgentPolicies = createSelector(getState, (state) => state);
|
||||||
|
|
||||||
export const selectPrivateLocationFlyoutVisible = (state: AppState) =>
|
export const selectAddingNewPrivateLocation = (state: AppState) =>
|
||||||
state.privateLocations.isPrivateLocationFlyoutVisible ?? false;
|
state.privateLocations.isCreatePrivateLocationFlyoutVisible ?? false;
|
||||||
|
|
||||||
export const selectPrivateLocationToEdit = (state: AppState) =>
|
|
||||||
state.privateLocations.privateLocationToEdit;
|
|
||||||
|
|
||||||
export const selectPrivateLocationsLoading = (state: AppState) =>
|
export const selectPrivateLocationsLoading = (state: AppState) =>
|
||||||
state.privateLocations.loading ?? false;
|
state.privateLocations.loading ?? false;
|
||||||
|
|
|
@ -14,7 +14,7 @@ const getState = (appState: AppState) => appState.agentPolicies;
|
||||||
export const selectAgentPolicies = createSelector(getState, (state) => state);
|
export const selectAgentPolicies = createSelector(getState, (state) => state);
|
||||||
|
|
||||||
export const selectAddingNewPrivateLocation = (state: AppState) =>
|
export const selectAddingNewPrivateLocation = (state: AppState) =>
|
||||||
state.privateLocations.isPrivateLocationFlyoutVisible ?? false;
|
state.privateLocations.isCreatePrivateLocationFlyoutVisible ?? false;
|
||||||
|
|
||||||
export const selectLocationMonitors = (state: AppState) => ({
|
export const selectLocationMonitors = (state: AppState) => ({
|
||||||
locationMonitors: state.dynamicSettings.locationMonitors,
|
locationMonitors: state.dynamicSettings.locationMonitors,
|
||||||
|
|
|
@ -116,7 +116,7 @@ export const mockState: SyntheticsAppState = {
|
||||||
data: null,
|
data: null,
|
||||||
},
|
},
|
||||||
privateLocations: {
|
privateLocations: {
|
||||||
isPrivateLocationFlyoutVisible: false,
|
isCreatePrivateLocationFlyoutVisible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
data: [],
|
data: [],
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { PrivateLocationAttributes } from '../runtime_types/private_locations';
|
||||||
import { PrivateLocationObject } from '../routes/settings/private_locations/add_private_location';
|
import { PrivateLocationObject } from '../routes/settings/private_locations/add_private_location';
|
||||||
import { RouteContext } from '../routes/types';
|
import { RouteContext } from '../routes/types';
|
||||||
import { privateLocationSavedObjectName } from '../../common/saved_objects/private_locations';
|
import { privateLocationSavedObjectName } from '../../common/saved_objects/private_locations';
|
||||||
import { EditPrivateLocationAttributes } from '../routes/settings/private_locations/edit_private_location';
|
|
||||||
|
|
||||||
export class PrivateLocationRepository {
|
export class PrivateLocationRepository {
|
||||||
internalSOClient: ISavedObjectsRepository;
|
internalSOClient: ISavedObjectsRepository;
|
||||||
|
@ -34,26 +33,6 @@ export class PrivateLocationRepository {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPrivateLocation(locationId: string) {
|
|
||||||
const { savedObjectsClient } = this.routeContext;
|
|
||||||
|
|
||||||
return savedObjectsClient.get<PrivateLocationAttributes>(
|
|
||||||
privateLocationSavedObjectName,
|
|
||||||
locationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editPrivateLocation(locationId: string, newAttributes: EditPrivateLocationAttributes) {
|
|
||||||
const { savedObjectsClient } = this.routeContext;
|
|
||||||
|
|
||||||
return savedObjectsClient.update<PrivateLocationAttributes>(
|
|
||||||
privateLocationSavedObjectName,
|
|
||||||
locationId,
|
|
||||||
newAttributes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async validatePrivateLocation() {
|
async validatePrivateLocation() {
|
||||||
const { response, request, server } = this.routeContext;
|
const { response, request, server } = this.routeContext;
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,6 @@ import { getDefaultAlertingRoute } from './default_alerts/get_default_alert';
|
||||||
import { createNetworkEventsRoute } from './network_events';
|
import { createNetworkEventsRoute } from './network_events';
|
||||||
import { addPrivateLocationRoute } from './settings/private_locations/add_private_location';
|
import { addPrivateLocationRoute } from './settings/private_locations/add_private_location';
|
||||||
import { deletePrivateLocationRoute } from './settings/private_locations/delete_private_location';
|
import { deletePrivateLocationRoute } from './settings/private_locations/delete_private_location';
|
||||||
import { editPrivateLocationRoute } from './settings/private_locations/edit_private_location';
|
|
||||||
import { getPrivateLocationsRoute } from './settings/private_locations/get_private_locations';
|
import { getPrivateLocationsRoute } from './settings/private_locations/get_private_locations';
|
||||||
import { getSyntheticsFilters } from './filters/filters';
|
import { getSyntheticsFilters } from './filters/filters';
|
||||||
import { getAllSyntheticsMonitorRoute } from './monitor_cruds/get_monitors_list';
|
import { getAllSyntheticsMonitorRoute } from './monitor_cruds/get_monitors_list';
|
||||||
|
@ -113,7 +112,6 @@ export const syntheticsAppPublicRestApiRoutes: SyntheticsRestApiRouteFactory[] =
|
||||||
deleteSyntheticsParamsRoute,
|
deleteSyntheticsParamsRoute,
|
||||||
addPrivateLocationRoute,
|
addPrivateLocationRoute,
|
||||||
deletePrivateLocationRoute,
|
deletePrivateLocationRoute,
|
||||||
editPrivateLocationRoute,
|
|
||||||
getPrivateLocationsRoute,
|
getPrivateLocationsRoute,
|
||||||
getAllSyntheticsMonitorRoute,
|
getAllSyntheticsMonitorRoute,
|
||||||
getSyntheticsMonitorRoute,
|
getSyntheticsMonitorRoute,
|
||||||
|
|
|
@ -83,10 +83,7 @@ export const syncEditedMonitorBulk = async ({
|
||||||
} as unknown as MonitorFields,
|
} as unknown as MonitorFields,
|
||||||
}));
|
}));
|
||||||
const [editedMonitorSavedObjects, editSyncResponse] = await Promise.all([
|
const [editedMonitorSavedObjects, editSyncResponse] = await Promise.all([
|
||||||
monitorConfigRepository.bulkUpdate({
|
monitorConfigRepository.bulkUpdate({ monitors: data }),
|
||||||
monitors: data,
|
|
||||||
namespace: spaceId !== routeContext.spaceId ? spaceId : undefined,
|
|
||||||
}),
|
|
||||||
syncUpdatedMonitors({ monitorsToUpdate, routeContext, spaceId, privateLocations }),
|
syncUpdatedMonitors({ monitorsToUpdate, routeContext, spaceId, privateLocations }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -1,163 +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 { TypeOf, schema } from '@kbn/config-schema';
|
|
||||||
import { SavedObject, SavedObjectsErrorHelpers } from '@kbn/core/server';
|
|
||||||
import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
import { getPrivateLocations } from '../../../synthetics_service/get_private_locations';
|
|
||||||
import { PrivateLocationAttributes } from '../../../runtime_types/private_locations';
|
|
||||||
import { PrivateLocationRepository } from '../../../repositories/private_location_repository';
|
|
||||||
import { PRIVATE_LOCATION_WRITE_API } from '../../../feature';
|
|
||||||
import { SyntheticsRestApiRouteFactory } from '../../types';
|
|
||||||
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
|
|
||||||
import { toClientContract, updatePrivateLocationMonitors } from './helpers';
|
|
||||||
import { PrivateLocation } from '../../../../common/runtime_types';
|
|
||||||
import { parseArrayFilters } from '../../common';
|
|
||||||
|
|
||||||
const EditPrivateLocationSchema = schema.object({
|
|
||||||
label: schema.maybe(
|
|
||||||
schema.string({
|
|
||||||
minLength: 1,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
tags: schema.maybe(schema.arrayOf(schema.string())),
|
|
||||||
});
|
|
||||||
|
|
||||||
const EditPrivateLocationQuery = schema.object({
|
|
||||||
locationId: schema.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type EditPrivateLocationAttributes = Pick<
|
|
||||||
PrivateLocationAttributes,
|
|
||||||
keyof TypeOf<typeof EditPrivateLocationSchema>
|
|
||||||
>;
|
|
||||||
|
|
||||||
const isPrivateLocationLabelChanged = (oldLabel: string, newLabel?: string): newLabel is string => {
|
|
||||||
return typeof newLabel === 'string' && oldLabel !== newLabel;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isPrivateLocationChanged = ({
|
|
||||||
privateLocation,
|
|
||||||
newParams,
|
|
||||||
}: {
|
|
||||||
privateLocation: SavedObject<PrivateLocationAttributes>;
|
|
||||||
newParams: TypeOf<typeof EditPrivateLocationSchema>;
|
|
||||||
}) => {
|
|
||||||
const isLabelChanged = isPrivateLocationLabelChanged(
|
|
||||||
privateLocation.attributes.label,
|
|
||||||
newParams.label
|
|
||||||
);
|
|
||||||
const areTagsChanged =
|
|
||||||
Array.isArray(newParams.tags) &&
|
|
||||||
(!privateLocation.attributes.tags ||
|
|
||||||
(privateLocation.attributes.tags &&
|
|
||||||
!isEqual(privateLocation.attributes.tags, newParams.tags)));
|
|
||||||
|
|
||||||
return isLabelChanged || areTagsChanged;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const editPrivateLocationRoute: SyntheticsRestApiRouteFactory<
|
|
||||||
PrivateLocation,
|
|
||||||
TypeOf<typeof EditPrivateLocationQuery>,
|
|
||||||
any,
|
|
||||||
TypeOf<typeof EditPrivateLocationSchema>
|
|
||||||
> = () => ({
|
|
||||||
method: 'PUT',
|
|
||||||
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + '/{locationId}',
|
|
||||||
validate: {},
|
|
||||||
validation: {
|
|
||||||
request: {
|
|
||||||
body: EditPrivateLocationSchema,
|
|
||||||
params: EditPrivateLocationQuery,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requiredPrivileges: [PRIVATE_LOCATION_WRITE_API],
|
|
||||||
handler: async (routeContext) => {
|
|
||||||
const { response, request, savedObjectsClient, server } = routeContext;
|
|
||||||
const { locationId } = request.params;
|
|
||||||
const { label: newLocationLabel, tags: newTags } = request.body;
|
|
||||||
|
|
||||||
const repo = new PrivateLocationRepository(routeContext);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { filtersStr } = parseArrayFilters({
|
|
||||||
locations: [locationId],
|
|
||||||
});
|
|
||||||
const [existingLocation, monitorsInLocation] = await Promise.all([
|
|
||||||
repo.getPrivateLocation(locationId),
|
|
||||||
routeContext.monitorConfigRepository.findDecryptedMonitors({
|
|
||||||
spaceId: ALL_SPACES_ID,
|
|
||||||
filter: filtersStr,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let newLocation: Awaited<ReturnType<typeof repo.editPrivateLocation>> | undefined;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isPrivateLocationChanged({ privateLocation: existingLocation, newParams: request.body })
|
|
||||||
) {
|
|
||||||
// This privileges check is done only when changing the label, because changing the label will update also the monitors in that location
|
|
||||||
if (isPrivateLocationLabelChanged(existingLocation.attributes.label, newLocationLabel)) {
|
|
||||||
const monitorsSpaces = monitorsInLocation.map(({ namespaces }) => namespaces![0]);
|
|
||||||
|
|
||||||
const checkSavedObjectsPrivileges =
|
|
||||||
server.security.authz.checkSavedObjectsPrivilegesWithRequest(request);
|
|
||||||
|
|
||||||
const { hasAllRequested } = await checkSavedObjectsPrivileges(
|
|
||||||
'saved_object:synthetics-monitor/bulk_update',
|
|
||||||
monitorsSpaces
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasAllRequested) {
|
|
||||||
return response.forbidden({
|
|
||||||
body: {
|
|
||||||
message: i18n.translate('xpack.synthetics.editPrivateLocation.forbidden', {
|
|
||||||
defaultMessage:
|
|
||||||
'You do not have sufficient permissions to update monitors in all required spaces. This private location is used by monitors in spaces where you lack update privileges.',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newLocation = await repo.editPrivateLocation(locationId, {
|
|
||||||
label: newLocationLabel || existingLocation.attributes.label,
|
|
||||||
tags: newTags || existingLocation.attributes.tags,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isPrivateLocationLabelChanged(existingLocation.attributes.label, newLocationLabel)) {
|
|
||||||
await updatePrivateLocationMonitors({
|
|
||||||
locationId,
|
|
||||||
newLocationLabel,
|
|
||||||
allPrivateLocations: await getPrivateLocations(savedObjectsClient),
|
|
||||||
routeContext,
|
|
||||||
monitorsInLocation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toClientContract({
|
|
||||||
...existingLocation,
|
|
||||||
attributes: {
|
|
||||||
...existingLocation.attributes,
|
|
||||||
...(newLocation ? newLocation.attributes : {}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
|
||||||
return response.notFound({
|
|
||||||
body: {
|
|
||||||
message: `Private location with id ${locationId} does not exist.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -5,20 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { allLocationsToClientContract, updatePrivateLocationMonitors } from './helpers';
|
import { allLocationsToClientContract } from './helpers';
|
||||||
import { RouteContext } from '../../types';
|
|
||||||
|
|
||||||
// Mock the syncEditedMonitorBulk module
|
|
||||||
jest.mock('../../monitor_cruds/bulk_cruds/edit_monitor_bulk', () => ({
|
|
||||||
syncEditedMonitorBulk: jest.fn().mockResolvedValue({
|
|
||||||
failedConfigs: [],
|
|
||||||
errors: [],
|
|
||||||
editedMonitors: [],
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Import the mocked function
|
|
||||||
import { syncEditedMonitorBulk } from '../../monitor_cruds/bulk_cruds/edit_monitor_bulk';
|
|
||||||
|
|
||||||
const testLocations = {
|
const testLocations = {
|
||||||
locations: [
|
locations: [
|
||||||
|
@ -127,107 +114,3 @@ describe('toClientContract', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updatePrivateLocationMonitors', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
const LOCATION_ID = 'test-location-id';
|
|
||||||
const NEW_LABEL = 'New location label';
|
|
||||||
const FIRST_SPACE_ID = 'firstSpaceId';
|
|
||||||
const SECOND_SPACE_ID = 'secondSpaceId';
|
|
||||||
const FIRST_MONITOR_ID = 'monitor-1';
|
|
||||||
const SECOND_MONITOR_ID = 'monitor-2';
|
|
||||||
const mockMonitors = [
|
|
||||||
{
|
|
||||||
id: FIRST_MONITOR_ID,
|
|
||||||
attributes: {
|
|
||||||
name: 'Test Monitor 1',
|
|
||||||
locations: [{ id: LOCATION_ID, label: 'Old Label' }],
|
|
||||||
// Other required monitor fields
|
|
||||||
type: 'http',
|
|
||||||
enabled: true,
|
|
||||||
schedule: { number: '10', unit: 'm' },
|
|
||||||
namespace: FIRST_SPACE_ID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: SECOND_MONITOR_ID,
|
|
||||||
attributes: {
|
|
||||||
name: 'Test Monitor 2',
|
|
||||||
locations: [
|
|
||||||
{ id: LOCATION_ID, label: 'Old Label' },
|
|
||||||
{ id: 'different-location', label: 'Different Location' },
|
|
||||||
],
|
|
||||||
// Other required monitor fields
|
|
||||||
type: 'http',
|
|
||||||
enabled: true,
|
|
||||||
schedule: { number: '5', unit: 'm' },
|
|
||||||
namespace: SECOND_SPACE_ID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
it('updates monitor locations with the new label', async () => {
|
|
||||||
const PRIVATE_LOCATIONS = [] as any[];
|
|
||||||
const ROUTE_CONTEXT = {} as RouteContext;
|
|
||||||
// Call the function
|
|
||||||
await updatePrivateLocationMonitors({
|
|
||||||
locationId: LOCATION_ID,
|
|
||||||
newLocationLabel: NEW_LABEL,
|
|
||||||
allPrivateLocations: PRIVATE_LOCATIONS,
|
|
||||||
routeContext: ROUTE_CONTEXT,
|
|
||||||
monitorsInLocation: mockMonitors as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify that syncEditedMonitorBulk was called
|
|
||||||
expect(syncEditedMonitorBulk).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
// Check first call for first space
|
|
||||||
expect(syncEditedMonitorBulk).toHaveBeenCalledWith({
|
|
||||||
monitorsToUpdate: expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
decryptedPreviousMonitor: mockMonitors[0],
|
|
||||||
normalizedMonitor: expect.any(Object),
|
|
||||||
monitorWithRevision: expect.objectContaining({
|
|
||||||
locations: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: LOCATION_ID,
|
|
||||||
label: NEW_LABEL,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
privateLocations: PRIVATE_LOCATIONS,
|
|
||||||
routeContext: ROUTE_CONTEXT,
|
|
||||||
spaceId: FIRST_SPACE_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check second call for second space
|
|
||||||
expect(syncEditedMonitorBulk).toHaveBeenCalledWith({
|
|
||||||
monitorsToUpdate: expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
decryptedPreviousMonitor: mockMonitors[1],
|
|
||||||
normalizedMonitor: expect.any(Object),
|
|
||||||
monitorWithRevision: expect.objectContaining({
|
|
||||||
locations: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: LOCATION_ID,
|
|
||||||
label: NEW_LABEL,
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
id: 'different-location',
|
|
||||||
label: 'Different Location',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
privateLocations: PRIVATE_LOCATIONS,
|
|
||||||
routeContext: ROUTE_CONTEXT,
|
|
||||||
spaceId: SECOND_SPACE_ID,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -4,24 +4,14 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import { SavedObject, SavedObjectsFindResult } from '@kbn/core/server';
|
import { SavedObject } from '@kbn/core/server';
|
||||||
import { formatSecrets, normalizeSecrets } from '../../../synthetics_service/utils';
|
|
||||||
import { AgentPolicyInfo } from '../../../../common/types';
|
import { AgentPolicyInfo } from '../../../../common/types';
|
||||||
import type {
|
import type { SyntheticsPrivateLocations } from '../../../../common/runtime_types';
|
||||||
SyntheticsMonitor,
|
|
||||||
SyntheticsMonitorWithSecretsAttributes,
|
|
||||||
SyntheticsPrivateLocations,
|
|
||||||
} from '../../../../common/runtime_types';
|
|
||||||
import type {
|
import type {
|
||||||
SyntheticsPrivateLocationsAttributes,
|
SyntheticsPrivateLocationsAttributes,
|
||||||
PrivateLocationAttributes,
|
PrivateLocationAttributes,
|
||||||
} from '../../../runtime_types/private_locations';
|
} from '../../../runtime_types/private_locations';
|
||||||
import { PrivateLocation } from '../../../../common/runtime_types';
|
import { PrivateLocation } from '../../../../common/runtime_types';
|
||||||
import {
|
|
||||||
MonitorConfigUpdate,
|
|
||||||
syncEditedMonitorBulk,
|
|
||||||
} from '../../monitor_cruds/bulk_cruds/edit_monitor_bulk';
|
|
||||||
import { RouteContext } from '../../types';
|
|
||||||
|
|
||||||
export const toClientContract = (
|
export const toClientContract = (
|
||||||
locationObject: SavedObject<PrivateLocationAttributes>
|
locationObject: SavedObject<PrivateLocationAttributes>
|
||||||
|
@ -70,54 +60,3 @@ export const toSavedObjectContract = (location: PrivateLocation): PrivateLocatio
|
||||||
spaces: location.spaces,
|
spaces: location.spaces,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// This should be called when changing the label of a private location because the label is also stored
|
|
||||||
// in the locations array of monitors attributes
|
|
||||||
export const updatePrivateLocationMonitors = async ({
|
|
||||||
locationId,
|
|
||||||
newLocationLabel,
|
|
||||||
allPrivateLocations,
|
|
||||||
routeContext,
|
|
||||||
monitorsInLocation,
|
|
||||||
}: {
|
|
||||||
locationId: string;
|
|
||||||
newLocationLabel: string;
|
|
||||||
allPrivateLocations: SyntheticsPrivateLocations;
|
|
||||||
routeContext: RouteContext;
|
|
||||||
monitorsInLocation: Array<SavedObjectsFindResult<SyntheticsMonitorWithSecretsAttributes>>;
|
|
||||||
}) => {
|
|
||||||
const updatedMonitorsPerSpace = monitorsInLocation.reduce<Record<string, MonitorConfigUpdate[]>>(
|
|
||||||
(acc, m) => {
|
|
||||||
const decryptedMonitorsWithNormalizedSecrets: SavedObject<SyntheticsMonitor> =
|
|
||||||
normalizeSecrets(m);
|
|
||||||
const normalizedMonitor = decryptedMonitorsWithNormalizedSecrets.attributes;
|
|
||||||
const newLocations = m.attributes.locations.map((l) =>
|
|
||||||
l.id !== locationId ? l : { ...l, label: newLocationLabel }
|
|
||||||
);
|
|
||||||
const monitorWithRevision = formatSecrets({ ...normalizedMonitor, locations: newLocations });
|
|
||||||
const monitorToUpdate: MonitorConfigUpdate = {
|
|
||||||
normalizedMonitor,
|
|
||||||
decryptedPreviousMonitor: m,
|
|
||||||
monitorWithRevision,
|
|
||||||
};
|
|
||||||
|
|
||||||
const namespace = m.attributes.namespace;
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[namespace]: [...(acc[namespace] || []), monitorToUpdate],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
const promises = Object.keys(updatedMonitorsPerSpace).map((namespace) => [
|
|
||||||
syncEditedMonitorBulk({
|
|
||||||
monitorsToUpdate: updatedMonitorsPerSpace[namespace],
|
|
||||||
privateLocations: allPrivateLocations,
|
|
||||||
routeContext,
|
|
||||||
spaceId: namespace,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return Promise.all(promises.flat());
|
|
||||||
};
|
|
||||||
|
|
|
@ -82,20 +82,17 @@ export class MonitorConfigRepository {
|
||||||
|
|
||||||
async bulkUpdate({
|
async bulkUpdate({
|
||||||
monitors,
|
monitors,
|
||||||
namespace,
|
|
||||||
}: {
|
}: {
|
||||||
monitors: Array<{
|
monitors: Array<{
|
||||||
attributes: MonitorFields;
|
attributes: MonitorFields;
|
||||||
id: string;
|
id: string;
|
||||||
}>;
|
}>;
|
||||||
namespace?: string;
|
|
||||||
}) {
|
}) {
|
||||||
return this.soClient.bulkUpdate<MonitorFields>(
|
return await this.soClient.bulkUpdate<MonitorFields>(
|
||||||
monitors.map(({ attributes, id }) => ({
|
monitors.map(({ attributes, id }) => ({
|
||||||
type: syntheticsMonitorType,
|
type: syntheticsMonitorType,
|
||||||
id,
|
id,
|
||||||
attributes,
|
attributes,
|
||||||
namespace,
|
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function normalizeMonitorSecretAttributes(
|
||||||
const normalizedMonitorAttributes = {
|
const normalizedMonitorAttributes = {
|
||||||
...defaultFields,
|
...defaultFields,
|
||||||
...monitor,
|
...monitor,
|
||||||
...JSON.parse(monitor.secrets || '{}'),
|
...JSON.parse(monitor.secrets || ''),
|
||||||
};
|
};
|
||||||
delete normalizedMonitorAttributes.secrets;
|
delete normalizedMonitorAttributes.secrets;
|
||||||
return normalizedMonitorAttributes;
|
return normalizedMonitorAttributes;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const GETTING_STARTED_ROUTE = '/monitors/getting-started';
|
||||||
|
|
||||||
export const SETTINGS_ROUTE = '/settings';
|
export const SETTINGS_ROUTE = '/settings';
|
||||||
|
|
||||||
export const PRIVATE_LOCATIONS_ROUTE = '/settings/private-locations';
|
export const PRIVATE_LOCATIOSN_ROUTE = '/settings/private-locations';
|
||||||
|
|
||||||
export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId';
|
export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId';
|
||||||
|
|
||||||
|
|
|
@ -39,10 +39,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
let resp;
|
let resp;
|
||||||
const { statusCodes, SPACE_ID, username, password, writeAccess, readUser } = options;
|
const { statusCodes, SPACE_ID, username, password, writeAccess, readUser } = options;
|
||||||
let tags = !writeAccess ? '[uptime-read]' : options.tags ?? '[uptime-read,uptime-write]';
|
let tags = !writeAccess ? '[uptime-read]' : options.tags ?? '[uptime-read,uptime-write]';
|
||||||
if (
|
if ((method === 'POST' || method === 'DELETE') && path.includes('private_locations')) {
|
||||||
(method === 'POST' || method === 'DELETE' || method === 'PUT') &&
|
|
||||||
path.includes('private_locations')
|
|
||||||
) {
|
|
||||||
tags = readUser
|
tags = readUser
|
||||||
? '[private-location-write,uptime-write]'
|
? '[private-location-write,uptime-write]'
|
||||||
: '[uptime-read,private-location-write,uptime-write]';
|
: '[uptime-read,private-location-write,uptime-write]';
|
||||||
|
|
|
@ -1,174 +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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
|
|
||||||
import { PrivateLocation, ServiceLocation } from '@kbn/synthetics-plugin/common/runtime_types';
|
|
||||||
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
|
||||||
import expect from '@kbn/expect';
|
|
||||||
import rawExpect from 'expect';
|
|
||||||
import { PackagePolicy } from '@kbn/fleet-plugin/common';
|
|
||||||
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
|
|
||||||
import { getFixtureJson } from './helpers/get_fixture_json';
|
|
||||||
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
|
|
||||||
import { addMonitorAPIHelper, omitMonitorKeys } from './create_monitor';
|
|
||||||
import { SupertestWithRoleScopeType } from '../../../services';
|
|
||||||
|
|
||||||
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|
||||||
describe('EditPrivateLocation', function () {
|
|
||||||
const kibanaServer = getService('kibanaServer');
|
|
||||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
|
||||||
const samlAuth = getService('samlAuth');
|
|
||||||
const roleScopedSupertest = getService('roleScopedSupertest');
|
|
||||||
let supertestEditorWithApiKey: SupertestWithRoleScopeType;
|
|
||||||
|
|
||||||
let testFleetPolicyID: string;
|
|
||||||
let editorUser: RoleCredentials;
|
|
||||||
let privateLocations: PrivateLocation[] = [];
|
|
||||||
const testPolicyName = 'Fleet test server policy' + Date.now();
|
|
||||||
|
|
||||||
let newMonitor: { id: string; name: string };
|
|
||||||
const testPrivateLocations = new PrivateLocationTestService(getService);
|
|
||||||
const NEW_LOCATION_LABEL = 'Barcelona';
|
|
||||||
const NEW_TAGS = ['myAwesomeTag'];
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
supertestEditorWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('editor', {
|
|
||||||
withInternalHeaders: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await kibanaServer.savedObjects.cleanStandardList();
|
|
||||||
await testPrivateLocations.installSyntheticsPackage();
|
|
||||||
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await supertestEditorWithApiKey.destroy();
|
|
||||||
await samlAuth.invalidateM2mApiKeyWithRoleScope(editorUser);
|
|
||||||
await kibanaServer.savedObjects.cleanStandardList();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds a test fleet policy', async () => {
|
|
||||||
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
|
|
||||||
testFleetPolicyID = apiResponse.body.item.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('add a test private location', async () => {
|
|
||||||
privateLocations = await testPrivateLocations.setTestLocations([testFleetPolicyID]);
|
|
||||||
|
|
||||||
const apiResponse = await supertestEditorWithApiKey
|
|
||||||
.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const testResponse: Array<PrivateLocation | ServiceLocation> = [
|
|
||||||
{
|
|
||||||
id: testFleetPolicyID,
|
|
||||||
isServiceManaged: false,
|
|
||||||
isInvalid: false,
|
|
||||||
label: privateLocations[0].label,
|
|
||||||
geo: {
|
|
||||||
lat: 0,
|
|
||||||
lon: 0,
|
|
||||||
},
|
|
||||||
agentPolicyId: testFleetPolicyID,
|
|
||||||
spaces: ['default'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
rawExpect(apiResponse.body.locations).toEqual(rawExpect.arrayContaining(testResponse));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds a monitor in private location', async () => {
|
|
||||||
newMonitor = {
|
|
||||||
...getFixtureJson('http_monitor'),
|
|
||||||
namespace: 'default',
|
|
||||||
locations: [privateLocations[0]],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { body, rawBody } = await addMonitorAPIHelper(
|
|
||||||
supertestWithoutAuth,
|
|
||||||
newMonitor,
|
|
||||||
200,
|
|
||||||
editorUser,
|
|
||||||
samlAuth
|
|
||||||
);
|
|
||||||
expect(body).eql(omitMonitorKeys(newMonitor));
|
|
||||||
newMonitor.id = rawBody.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('successfully edits a private location label', async () => {
|
|
||||||
const privateLocation = privateLocations[0];
|
|
||||||
|
|
||||||
// Edit the private location
|
|
||||||
const editResponse = await supertestEditorWithApiKey
|
|
||||||
.put(`${SYNTHETICS_API_URLS.PRIVATE_LOCATIONS}/${privateLocation.id}`)
|
|
||||||
.send({ label: NEW_LOCATION_LABEL, tags: NEW_TAGS })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
// Verify the response contains the updated label
|
|
||||||
expect(editResponse.body.label).to.be(NEW_LOCATION_LABEL);
|
|
||||||
expect(editResponse.body.tags).to.eql(NEW_TAGS);
|
|
||||||
expect(editResponse.body.id).to.be(privateLocation.id);
|
|
||||||
expect(editResponse.body.agentPolicyId).to.be(privateLocation.agentPolicyId);
|
|
||||||
|
|
||||||
// Verify the location was actually updated by getting it
|
|
||||||
const getResponse = await supertestEditorWithApiKey
|
|
||||||
.get(`${SYNTHETICS_API_URLS.PRIVATE_LOCATIONS}/${privateLocation.id}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(getResponse.body.label).to.be(NEW_LOCATION_LABEL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('verifies that monitor location label is updated when the private location label changes', async () => {
|
|
||||||
// Get the monitor with the updated location label
|
|
||||||
const getMonitorResponse = await supertestEditorWithApiKey
|
|
||||||
.get(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${newMonitor.id}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
// Verify the monitor's location has the updated label
|
|
||||||
const monitor = getMonitorResponse.body;
|
|
||||||
expect(monitor.locations).to.have.length(1);
|
|
||||||
expect(monitor.locations[0].id).to.be(privateLocations[0].id);
|
|
||||||
expect(monitor.locations[0].label).to.be(NEW_LOCATION_LABEL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('verifies that package policies are updated when the private location label changes', async () => {
|
|
||||||
const apiResponse = await supertestEditorWithApiKey.get(
|
|
||||||
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
|
|
||||||
);
|
|
||||||
|
|
||||||
const packagePolicy: PackagePolicy = apiResponse.body.items.find(
|
|
||||||
(pkgPolicy: PackagePolicy) =>
|
|
||||||
pkgPolicy.id === newMonitor.id + '-' + testFleetPolicyID + '-default'
|
|
||||||
);
|
|
||||||
expect(packagePolicy.name).to.contain(NEW_LOCATION_LABEL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 404 when trying to edit a non-existent private location', async () => {
|
|
||||||
const nonExistentId = 'non-existent-id';
|
|
||||||
|
|
||||||
const response = await supertestEditorWithApiKey
|
|
||||||
.put(`${SYNTHETICS_API_URLS.PRIVATE_LOCATIONS}/${nonExistentId}`)
|
|
||||||
.send({ label: NEW_LOCATION_LABEL })
|
|
||||||
.expect(404);
|
|
||||||
|
|
||||||
expect(response.body.message).to.contain(
|
|
||||||
`Private location with id ${nonExistentId} does not exist.`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 400 when trying to edit with an empty label', async () => {
|
|
||||||
const response = await supertestEditorWithApiKey
|
|
||||||
.put(`${SYNTHETICS_API_URLS.PRIVATE_LOCATIONS}/${privateLocations[0].id}`)
|
|
||||||
.send({ label: '' })
|
|
||||||
.expect(400);
|
|
||||||
|
|
||||||
expect(response.body.message).to.contain(
|
|
||||||
'[request body.label]: value has length [0] but it must have a minimum length of [1].'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -31,6 +31,5 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext)
|
||||||
loadTestFile(require.resolve('./sync_global_params'));
|
loadTestFile(require.resolve('./sync_global_params'));
|
||||||
loadTestFile(require.resolve('./synthetics_enablement'));
|
loadTestFile(require.resolve('./synthetics_enablement'));
|
||||||
loadTestFile(require.resolve('./test_now_monitor'));
|
loadTestFile(require.resolve('./test_now_monitor'));
|
||||||
loadTestFile(require.resolve('./edit_private_location'));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue