Skip to content

Commit 2c9adae

Browse files
cesco-fshahzad31
andauthored
[8.19] [Synthetics] Edit private locations labels and tags in Synthetics (#221515) (#224639)
# Backport This will backport the following commits from `main` to `8.19`: - [[Synthetics] Edit private locations labels and tags in Synthetics (#221515)](#221515) <!--- Backport version: 10.0.1 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Francesco Fagnani","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-06-20T07:57:13Z","message":"[Synthetics] Edit private locations labels and tags in Synthetics (#221515)\n\nThis PR closes #221508.\n\n\n\nhttps://github.com/user-attachments/assets/0b57487a-7188-4722-99dc-5cb44c15f129\n\n- Added a new API endpoint to edit the label of private locations.\n- When a label is updated, all monitors deployed in that location are\nautomatically updated to reflect the change.\n- The UI now allows users to edit only the label of a private location.\n- Added comprehensive API tests to cover the new functionality.\n\n---------\n\nCo-authored-by: kibanamachine <[email protected]>","sha":"b1b8fb0a8851d3e64dd0c24c7c42ebd144de24b8","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:feature","ci:project-deploy-observability","Team:obs-ux-management","backport:version","v9.1.0","v8.19.0","author:obs-ux-management"],"title":"[Synthetics] Edit private locations labels and tags in Synthetics","number":221515,"url":"https://github.com/elastic/kibana/pull/221515","mergeCommit":{"message":"[Synthetics] Edit private locations labels and tags in Synthetics (#221515)\n\nThis PR closes #221508.\n\n\n\nhttps://github.com/user-attachments/assets/0b57487a-7188-4722-99dc-5cb44c15f129\n\n- Added a new API endpoint to edit the label of private locations.\n- When a label is updated, all monitors deployed in that location are\nautomatically updated to reflect the change.\n- The UI now allows users to edit only the label of a private location.\n- Added comprehensive API tests to cover the new functionality.\n\n---------\n\nCo-authored-by: kibanamachine <[email protected]>","sha":"b1b8fb0a8851d3e64dd0c24c7c42ebd144de24b8"}},"sourceBranch":"main","suggestedTargetBranches":["8.19"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/221515","number":221515,"mergeCommit":{"message":"[Synthetics] Edit private locations labels and tags in Synthetics (#221515)\n\nThis PR closes #221508.\n\n\n\nhttps://github.com/user-attachments/assets/0b57487a-7188-4722-99dc-5cb44c15f129\n\n- Added a new API endpoint to edit the label of private locations.\n- When a label is updated, all monitors deployed in that location are\nautomatically updated to reflect the change.\n- The UI now allows users to edit only the label of a private location.\n- Added comprehensive API tests to cover the new functionality.\n\n---------\n\nCo-authored-by: kibanamachine <[email protected]>","sha":"b1b8fb0a8851d3e64dd0c24c7c42ebd144de24b8"}},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Shahzad <[email protected]>
1 parent 10aa64e commit 2c9adae

File tree

37 files changed

+1001
-133
lines changed

37 files changed

+1001
-133
lines changed

oas_docs/output/kibana.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22871,6 +22871,69 @@ paths:
2287122871
summary: Get a private location
2287222872
tags:
2287322873
- synthetics
22874+
put:
22875+
description: |
22876+
Update an existing private location's label.
22877+
You must have `all` privileges for the Synthetics and Uptime feature in the Observability section of the Kibana feature privileges.
22878+
When a private location's label is updated, all monitors using this location will also be updated to maintain data consistency.
22879+
operationId: put-private-location
22880+
parameters:
22881+
- description: The unique identifier of the private location to be updated.
22882+
in: path
22883+
name: id
22884+
required: true
22885+
schema:
22886+
type: string
22887+
requestBody:
22888+
content:
22889+
application/json:
22890+
examples:
22891+
putPrivateLocationRequestExample1:
22892+
description: Update a private location's label.
22893+
value: |-
22894+
{
22895+
"label": "Updated Private Location Name"
22896+
}
22897+
schema:
22898+
type: object
22899+
properties:
22900+
label:
22901+
description: A new label for the private location. Must be at least 1 character long.
22902+
minLength: 1
22903+
type: string
22904+
required:
22905+
- label
22906+
required: true
22907+
responses:
22908+
'200':
22909+
content:
22910+
application/json:
22911+
examples:
22912+
putPrivateLocationResponseExample1:
22913+
value: |-
22914+
{
22915+
"label": "Updated Private Location Name",
22916+
"id": "test-private-location-id",
22917+
"agentPolicyId": "test-private-location-id",
22918+
"isServiceManaged": false,
22919+
"isInvalid": false,
22920+
"tags": ["private", "testing", "updated"],
22921+
"geo": {
22922+
"lat": 37.7749,
22923+
"lon": -122.4194
22924+
},
22925+
"spaces": ["*"]
22926+
}
22927+
schema:
22928+
$ref: '#/components/schemas/Synthetics_getPrivateLocation'
22929+
description: A successful response.
22930+
'400':
22931+
description: If the `label` is shorter than 1 character the API will return a 400 Bad Request response with a corresponding error message.
22932+
'404':
22933+
description: If the private location with the specified ID does not exist, the API will return a 404 Not Found response.
22934+
summary: Update a private location
22935+
tags:
22936+
- synthetics
2287422937
/api/task_manager/_health:
2287522938
get:
2287622939
description: |

x-pack/solutions/observability/plugins/synthetics/common/constants/ui.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const GETTING_STARTED_ROUTE = '/monitors/getting-started';
2828

2929
export const SETTINGS_ROUTE = '/settings';
3030

31-
export const PRIVATE_LOCATIOSN_ROUTE = '/settings/private-locations';
31+
export const PRIVATE_LOCATIONS_ROUTE = '/settings/private-locations';
3232

3333
export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId';
3434

x-pack/solutions/observability/plugins/synthetics/docs/openapi/synthetic_apis.yaml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,71 @@ paths:
10241024
},
10251025
"namespace": "default"
10261026
}
1027+
put:
1028+
summary: Update a private location
1029+
operationId: put-private-location
1030+
description: >
1031+
Update an existing private location's label.
1032+
1033+
You must have `all` privileges for the Synthetics and Uptime feature in the Observability section of the Kibana feature privileges.
1034+
1035+
When a private location's label is updated, all monitors using this location will also be updated to maintain data consistency.
1036+
tags:
1037+
- synthetics
1038+
parameters:
1039+
- in: path
1040+
name: id
1041+
description: The unique identifier of the private location to be updated.
1042+
required: true
1043+
schema:
1044+
type: string
1045+
requestBody:
1046+
required: true
1047+
content:
1048+
application/json:
1049+
schema:
1050+
type: object
1051+
required:
1052+
- label
1053+
properties:
1054+
label:
1055+
type: string
1056+
minLength: 1
1057+
description: A new label for the private location. Must be at least 1 character long.
1058+
examples:
1059+
putPrivateLocationRequestExample1:
1060+
description: Update a private location's label.
1061+
value: |-
1062+
{
1063+
"label": "Updated Private Location Name"
1064+
}
1065+
responses:
1066+
'200':
1067+
description: A successful response.
1068+
content:
1069+
application/json:
1070+
schema:
1071+
$ref: "#/components/schemas/getPrivateLocation"
1072+
examples:
1073+
putPrivateLocationResponseExample1:
1074+
value: |-
1075+
{
1076+
"label": "Updated Private Location Name",
1077+
"id": "test-private-location-id",
1078+
"agentPolicyId": "test-private-location-id",
1079+
"isServiceManaged": false,
1080+
"isInvalid": false,
1081+
"tags": ["private", "testing", "updated"],
1082+
"geo": {
1083+
"lat": 37.7749,
1084+
"lon": -122.4194
1085+
},
1086+
"spaces": ["*"]
1087+
}
1088+
'400':
1089+
description: If the `label` is shorter than 1 character the API will return a 400 Bad Request response with a corresponding error message.
1090+
'404':
1091+
description: If the private location with the specified ID does not exist, the API will return a 404 Not Found response.
10271092
components:
10281093
schemas:
10291094
commonMonitorFields:

x-pack/solutions/observability/plugins/synthetics/e2e/synthetics/journeys/private_locations.journey.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { syntheticsAppPageProvider } from '../page_objects/synthetics_app';
1515
journey(`PrivateLocationsSettings`, async ({ page, params }) => {
1616
const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl, params });
1717
const services = new SyntheticsServices(params);
18+
const NEW_LOCATION_LABEL = 'Updated Test Location';
1819

1920
page.setDefaultTimeout(2 * 30000);
2021

@@ -87,11 +88,33 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => {
8788
});
8889
});
8990

91+
step('Edit private location label and verify disabled fields', async () => {
92+
// Click on the edit button for the location
93+
await page.click('[data-test-subj="action-edit"]');
94+
95+
// Verify that agent policy selector is disabled
96+
expect(await page.locator('[aria-label="Select agent policy"]').isDisabled()).toBe(true);
97+
98+
// Verify that tags field is disabled
99+
expect(await page.locator('[aria-label="Tags"]').isDisabled()).toBe(false);
100+
101+
// Verify that spaces selector is disabled
102+
expect(await page.locator('[aria-label="Spaces "]').isDisabled()).toBe(true);
103+
104+
await page.fill('[aria-label="Location name"]', NEW_LOCATION_LABEL);
105+
106+
// Save the changes
107+
await page.click('[data-test-subj="syntheticsLocationFlyoutSaveButton"]');
108+
109+
// Wait for the save to complete and verify the updated label appears in the table
110+
await page.waitForSelector(`td:has-text("${NEW_LOCATION_LABEL}")`);
111+
});
112+
90113
step('Integration cannot be edited in Fleet', async () => {
91114
await page.goto(`${params.kibanaUrl}/app/integrations/detail/synthetics/policies`);
92115
await page.waitForSelector('h1:has-text("Elastic Synthetics")');
93116

94-
await page.click('text="test-monitor-Test private-default"');
117+
await page.click(`text="test-monitor-${NEW_LOCATION_LABEL}-default"`);
95118
await page.waitForSelector('h1:has-text("Edit Elastic Synthetics integration")');
96119
await page.waitForSelector('text="This package policy is managed by the Synthetics app."');
97120
});
@@ -111,14 +134,14 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => {
111134
await page.click('h1:has-text("Settings")');
112135
await page.click('text=Private Locations');
113136
await page.waitForSelector('td:has-text("1")');
114-
await page.waitForSelector('td:has-text("Test private")');
137+
await page.waitForSelector(`td:has-text("${NEW_LOCATION_LABEL}")`);
115138
await page.click('.euiTableRowCell .euiToolTipAnchor');
116139
await page.click('button:has-text("Tags")');
117140
await page.click('[aria-label="Tags"] >> text=Area51');
118141
await page.click(
119142
'main div:has-text("Private locations allow you to run monitors from your own premises. They require")'
120143
);
121-
await page.click('text=Test private');
144+
await page.click(`text=${NEW_LOCATION_LABEL}`);
122145

123146
await page.click('.euiTableRowCell .euiToolTipAnchor');
124147

x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ describe('GettingStartedPage', () => {
2121
loading: false,
2222
privateLocations: [],
2323
deleteLoading: false,
24-
onSubmit: jest.fn(),
25-
onDelete: jest.fn(),
24+
onCreateLocationAPI: jest.fn(),
25+
onDeleteLocationAPI: jest.fn(),
26+
onEditLocationAPI: jest.fn(),
2627
createLoading: false,
2728
});
2829
jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true);
@@ -82,7 +83,7 @@ describe('GettingStartedPage', () => {
8283
loading: false,
8384
},
8485
privateLocations: {
85-
isCreatePrivateLocationFlyoutVisible: true,
86+
isPrivateLocationFlyoutVisible: true,
8687
},
8788
agentPolicies: {
8889
data: [],
@@ -112,7 +113,7 @@ describe('GettingStartedPage', () => {
112113
data: [{}],
113114
},
114115
privateLocations: {
115-
isCreatePrivateLocationFlyoutVisible: true,
116+
isPrivateLocationFlyoutVisible: true,
116117
},
117118
},
118119
});
@@ -151,7 +152,7 @@ describe('GettingStartedPage', () => {
151152
data: [{}],
152153
},
153154
privateLocations: {
154-
isCreatePrivateLocationFlyoutVisible: true,
155+
isPrivateLocationFlyoutVisible: true,
155156
},
156157
},
157158
}

x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_
2727
import { getServiceLocations, cleanMonitorListState } from '../../state';
2828
import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui';
2929
import { SimpleMonitorForm } from './simple_monitor_form';
30-
import { AddLocationFlyout, NewLocation } from '../settings/private_locations/add_location_flyout';
30+
import {
31+
AddOrEditLocationFlyout,
32+
NewLocation,
33+
} from '../settings/private_locations/add_or_edit_location_flyout';
3134
import type { ClientPluginsStart } from '../../../../plugin';
3235
import { getAgentPoliciesAction, selectAgentPolicies } from '../../state/agent_policies';
33-
import { selectAddingNewPrivateLocation } from '../../state/settings/selectors';
34-
import { setIsCreatePrivateLocationFlyoutVisible } from '../../state/private_locations/actions';
36+
import { setIsPrivateLocationFlyoutVisible } from '../../state/private_locations/actions';
37+
import { selectPrivateLocationFlyoutVisible } from '../../state/private_locations/selectors';
3538

3639
export const GettingStartedPage = () => {
3740
const dispatch = useDispatch();
@@ -127,23 +130,23 @@ export const GettingStartedOnPrem = () => {
127130

128131
useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview
129132

130-
const isAddingNewLocation = useSelector(selectAddingNewPrivateLocation);
133+
const isPrivateLocationFlyoutVisible = useSelector(selectPrivateLocationFlyoutVisible);
131134

132-
const setIsAddingNewLocation = useCallback(
133-
(val: boolean) => dispatch(setIsCreatePrivateLocationFlyoutVisible(val)),
135+
const setIsFlyoutOpen = useCallback(
136+
(val: boolean) => dispatch(setIsPrivateLocationFlyoutVisible(val)),
134137
[dispatch]
135138
);
136139

137-
const { onSubmit, privateLocations } = usePrivateLocationsAPI();
140+
const { onCreateLocationAPI, privateLocations } = usePrivateLocationsAPI();
138141

139142
const handleSubmit = (formData: NewLocation) => {
140-
onSubmit(formData);
143+
onCreateLocationAPI(formData);
141144
};
142145

143146
// make sure flyout is closed when first visiting the page
144147
useEffect(() => {
145-
setIsAddingNewLocation(false);
146-
}, [setIsAddingNewLocation]);
148+
setIsFlyoutOpen(false);
149+
}, [setIsFlyoutOpen]);
147150

148151
return (
149152
<>
@@ -163,7 +166,7 @@ export const GettingStartedOnPrem = () => {
163166
fill
164167
iconType="plusInCircleFilled"
165168
data-test-subj="gettingStartedAddLocationButton"
166-
onClick={() => setIsAddingNewLocation(true)}
169+
onClick={() => setIsFlyoutOpen(true)}
167170
>
168171
{CREATE_LOCATION_LABEL}
169172
</EuiButton>
@@ -173,9 +176,9 @@ export const GettingStartedOnPrem = () => {
173176
footer={<GettingStartedLink />}
174177
/>
175178

176-
{isAddingNewLocation ? (
177-
<AddLocationFlyout
178-
setIsOpen={setIsAddingNewLocation}
179+
{isPrivateLocationFlyoutVisible ? (
180+
<AddOrEditLocationFlyout
181+
onCloseFlyout={() => setIsFlyoutOpen(false)}
179182
onSubmit={handleSubmit}
180183
privateLocations={privateLocations}
181184
/>

x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/settings/components/spaces_select.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ import { ClientPluginsStart } from '../../../../../plugin';
1616

1717
interface SpaceSelectorProps {
1818
helpText: string;
19+
isDisabled?: boolean;
1920
}
2021

21-
export const SpaceSelector = <T extends FieldValues>({ helpText }: SpaceSelectorProps) => {
22+
export const SpaceSelector = <T extends FieldValues>({
23+
helpText,
24+
isDisabled = false,
25+
}: SpaceSelectorProps) => {
2226
const NAMESPACES_NAME = 'spaces' as Path<T>;
2327
const { services } = useKibana<ClientPluginsStart>();
2428
const [spacesList, setSpacesList] = React.useState<Array<{ id: string; label: string }>>([]);
@@ -61,6 +65,7 @@ export const SpaceSelector = <T extends FieldValues>({ helpText }: SpaceSelector
6165
rules={{ required: true }}
6266
render={({ field }) => (
6367
<EuiComboBox
68+
isDisabled={isDisabled}
6469
fullWidth
6570
aria-label={SPACES_LABEL}
6671
placeholder={SPACES_LABEL}

x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/settings/components/tags_field.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ export function TagsField({
1515
tagsList,
1616
control,
1717
errors,
18+
isDisabled,
1819
}: {
1920
tagsList: string[];
2021
errors: FieldErrors;
2122
control: Control<PrivateLocation, any>;
23+
isDisabled?: boolean;
2224
}) {
2325
return (
2426
<EuiFormRow fullWidth label={TAGS_LABEL}>
@@ -27,6 +29,7 @@ export function TagsField({
2729
control={control}
2830
render={({ field }) => (
2931
<EuiComboBox
32+
isDisabled={isDisabled}
3033
fullWidth
3134
aria-label={TAGS_LABEL}
3235
placeholder={TAGS_LABEL}

0 commit comments

Comments
 (0)