Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
1e35b0d
Define CCS settings and remote monitor io-ts types
mgiota Apr 10, 2026
a7c72dd
remove storedSyntheticsCCSSettingsSchema, not needed, it is a new SO
mgiota Apr 10, 2026
937691f
Add CCS settings API, saved object, and route wrapper wiring
mgiota Apr 10, 2026
1e1aeb8
Changes from node scripts/check_mappings_update --fix
kibanamachine Apr 10, 2026
82672ca
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 10, 2026
efb4d05
add unit tests for get_synthetics_indices and settings repository
mgiota Apr 10, 2026
825bce9
fix failing check_registered_types tests
mgiota Apr 10, 2026
53f7ba1
CI fixes
mgiota Apr 10, 2026
55db7fe
add maxSize to the remoteKibanaUrls schema
mgiota Apr 10, 2026
247e55a
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 10, 2026
d5a79c8
Add kibanaUrl deep-link resolution plumbing for CCS (#259268)
mgiota Apr 10, 2026
1ba8f33
add unit tests for remote_result_utils
mgiota Apr 10, 2026
a563e17
Decorate ping results with remote cluster info for CCS (#259267)
mgiota Apr 10, 2026
18f4d03
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 11, 2026
113ada6
Register synthetics-ccs-settings in the previouslyRegisteredTypes gua…
mgiota Apr 11, 2026
67dc454
Add remote cluster configuration UI to Synthetics Settings page (#259…
mgiota Apr 11, 2026
b6b39ca
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 11, 2026
5792e2b
fix jest mock
mgiota Apr 11, 2026
c8fa165
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 11, 2026
8912e77
fix forwardCompatibility CI error
mgiota Apr 11, 2026
6b6e5ee
fix useFetcher hook unnecessary dependency error
mgiota Apr 11, 2026
4cf4fc4
fix mock issue
mgiota Apr 11, 2026
307fba2
Changes from node scripts/jest_integration -u src/core/server/integra…
kibanamachine Apr 11, 2026
c716317
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 11, 2026
67f30f4
feat(synthetics): support remote monitors in overview status service
mgiota Apr 11, 2026
404d3fe
feat(synthetics): gate CCS settings UI behind experimental feature flag
mgiota Apr 11, 2026
ad62f59
fix type issues
mgiota Apr 11, 2026
2e4e96b
fix(synthetics): remove no-op ternary in remote monitor schedule field
mgiota Apr 11, 2026
14b6d7c
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 11, 2026
f2fac50
fix type issues
mgiota Apr 11, 2026
92ed41c
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 12, 2026
705d450
fix failing unit tests
mgiota Apr 12, 2026
6b38e73
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 12, 2026
a943bd7
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 12, 2026
f9d381c
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 12, 2026
f0293f0
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 12, 2026
d69c474
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 14, 2026
b525a35
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 14, 2026
209c664
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 14, 2026
deeccc9
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 14, 2026
e6759a6
Merge CCS settings into existing dynamic settings saved object
mgiota Apr 14, 2026
ef80ca7
Changes from node scripts/check_mappings_update --fix
kibanamachine Apr 14, 2026
11d7fc8
reverts saved object changes
mgiota Apr 14, 2026
577db60
fix failing unit tests
mgiota Apr 14, 2026
f2cfd29
fix(synthetics): gate remote monitor detection behind CCS feature flag
mgiota Apr 14, 2026
9ca5221
remove leftover artifacts after using existing dynamic settings saved…
mgiota Apr 14, 2026
a58a069
add isCCSEnabled utility helper
mgiota Apr 14, 2026
2648664
ccs test coverage
mgiota Apr 14, 2026
2e9bff7
fix(synthetics): reduce test memory footprint for overview status pag…
mgiota Apr 14, 2026
190913f
revert changes to overview_status_service, this will be handled in th…
mgiota Apr 15, 2026
4afce36
fix(synthetics): fix CCS settings form test mocking for isCCSEnabled …
mgiota Apr 15, 2026
ee18bcd
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 15, 2026
2869239
revert remoteKibanaUrls changes
mgiota Apr 15, 2026
dc70043
fix failing unit tests
mgiota Apr 15, 2026
461fd1c
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 15, 2026
dc28bb5
types cleanup
mgiota Apr 15, 2026
ebb6f5b
more test coverage
mgiota Apr 15, 2026
0e20e3c
Merge branch 'main' into ccs/types-and-settings-api
mgiota Apr 15, 2026
6149283
remove ccs_settings route & use existing dynamic_settings route
mgiota Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kbn-check-saved-objects-cli/current_fields.json
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,7 @@
],
"spaces-usage-stats": [],
"stream-prompts": [],
"synthetics-ccs-settings": [],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mgiota i wonder if we need to add an extra saved object type for this? why not keep those things part of existing settings saved object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shahzad31 Yep good point, I thought about that. The existing settings are a global singleton (namespaceType: 'single'), but CCS settings need to be per-space (multiple-isolated) so different spaces can configure different remote clusters. These namespace types are mutually exclusive on a single saved object type, so a separate type is necessary. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shahzad31 As discussed I used existing dynamic settings saved object. Revert changes in following commits e6759a6 and 11d7fc8

"synthetics-dynamic-settings": [],
"synthetics-monitor": [
"alert",
Expand Down
4 changes: 4 additions & 0 deletions packages/kbn-check-saved-objects-cli/current_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4403,6 +4403,10 @@
"dynamic": false,
"properties": {}
},
"synthetics-ccs-settings": {
"dynamic": false,
"properties": {}
},
"synthetics-dynamic-settings": {
"dynamic": false,
"properties": {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ export { registerCoreObjectTypes } from './registration';

// set minimum number of registered saved objects to ensure no object types are removed after 8.8
// declared in internal implementation explicitly to prevent unintended changes.
export const SAVED_OBJECT_TYPES_COUNT = 155 as const;
export const SAVED_OBJECT_TYPES_COUNT = 156 as const;
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"space": "a3792f667888f8a976a742c941bd6db28d027604eccc854986a5ec2515f44483",
"spaces-usage-stats": "f692e8d75a0ac9187bfbf237e1b5aa6f9a028cf5569a3df2f9e0e96e625b6215",
"stream-prompts": "25d761054f4e6c60362ef73da19e1bee4c22bcf79e858bdb11ef3eb7f433c66c",
"synthetics-ccs-settings": "a749c676195ab02b21fca8488de09b1f6be5c88805b1a68fd5be69fc8525075b",
"synthetics-dynamic-settings": "30883b9647f119e1d4529f49bc0fee459c56f62489993cd7f2397ffcb8608e63",
"synthetics-monitor": "19af677892a5eac45b3d1903c083cb222d8898d69ee545eaf079522b0bbbb85b",
"synthetics-monitor-multi-space": "9159e968b4c011ee11fe7887572e883e0928c4b4b0924d672c77003cfdf01fe0",
Expand Down Expand Up @@ -1247,6 +1248,11 @@ describe('checking migration metadata changes on all registered SO types', () =>
"stream-prompts|10.2.0: a79e6825ccd534ba98f6b8ce45357da62f162efe3fbad1d0c2c7d3c0f8d1e48c",
"stream-prompts|10.1.0: ba35cffdf735265c1b4bc2652c4ea22c74e81fafb58a5c67eba53a4ec3e821bc",
"=======================================================================================",
"synthetics-ccs-settings|global: 16e0ec35935c7ad1af8b0fda068c471452cb4ca7",
"synthetics-ccs-settings|mappings: e1b10e5bec060a176469a5e9a4f80c94e23abcd7",
"synthetics-ccs-settings|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709",
"synthetics-ccs-settings|10.1.0: 0c41207f2cf98a296785c7bbb2a631d6dc8236519a99be5bdbd2f21f6dd752e6",
"================================================================================================",
"synthetics-dynamic-settings|global: 79542e96ca9347188221391c3c7157ee5e8daefc",
"synthetics-dynamic-settings|mappings: e1b10e5bec060a176469a5e9a4f80c94e23abcd7",
"synthetics-dynamic-settings|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709",
Expand Down Expand Up @@ -1540,6 +1546,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"space": "10.2.0",
"spaces-usage-stats": "10.0.0",
"stream-prompts": "10.3.0",
"synthetics-ccs-settings": "10.1.0",
"synthetics-dynamic-settings": "10.0.0",
"synthetics-monitor": "10.2.0",
"synthetics-monitor-multi-space": "10.0.0",
Expand Down Expand Up @@ -1705,6 +1712,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"space": "10.2.0",
"spaces-usage-stats": "7.14.1",
"stream-prompts": "10.3.0",
"synthetics-ccs-settings": "10.1.0",
"synthetics-dynamic-settings": "0.0.0",
"synthetics-monitor": "10.2.0",
"synthetics-monitor-multi-space": "10.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ const previouslyRegisteredTypes = [
'space',
'spaces-usage-stats',
'stream-prompts',
'synthetics-ccs-settings',
'synthetics-monitor',
'synthetics-monitor-multi-space',
'synthetics-param',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ export enum SYNTHETICS_API_URLS {

INSPECT_STATUS_RULE = '/internal/synthetics/inspect_status_rule',
INSPECT_TLS_RULE = '/internal/synthetics/inspect_tls_rule',

CCS_SETTINGS = '/internal/synthetics/ccs_settings',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { SYNTHETICS_INDEX_PATTERN } from './constants';
import { getSyntheticsIndices } from './get_synthetics_indices';

describe('getSyntheticsIndices', () => {
it('returns only the local index when CCS is disabled', () => {
const result = getSyntheticsIndices({
useAllRemoteClusters: false,
selectedRemoteClusters: [],
});
expect(result).toStrictEqual([SYNTHETICS_INDEX_PATTERN]);
});

it('returns a wildcard remote and the local index when useAllRemoteClusters is true', () => {
const result = getSyntheticsIndices({
useAllRemoteClusters: true,
selectedRemoteClusters: [],
remoteClusters: [
{ name: 'cluster1', isConnected: true },
{ name: 'cluster2', isConnected: true },
],
});
expect(result).toStrictEqual([SYNTHETICS_INDEX_PATTERN, `*:${SYNTHETICS_INDEX_PATTERN}`]);
});

it('returns only connected clusters from the selected list', () => {
const result = getSyntheticsIndices({
useAllRemoteClusters: false,
selectedRemoteClusters: ['cluster1', 'cluster3'],
remoteClusters: [
{ name: 'cluster1', isConnected: true },
{ name: 'cluster2', isConnected: true },
{ name: 'cluster3', isConnected: false },
],
});
expect(result).toStrictEqual([
SYNTHETICS_INDEX_PATTERN,
`cluster1:${SYNTHETICS_INDEX_PATTERN}`,
]);
});

it('excludes clusters not in the selected list even if connected', () => {
const result = getSyntheticsIndices({
useAllRemoteClusters: false,
selectedRemoteClusters: ['cluster1'],
remoteClusters: [
{ name: 'cluster1', isConnected: true },
{ name: 'cluster2', isConnected: true },
],
});
expect(result).toStrictEqual([
SYNTHETICS_INDEX_PATTERN,
`cluster1:${SYNTHETICS_INDEX_PATTERN}`,
]);
});

it('returns only the local index when selected clusters are all disconnected', () => {
const result = getSyntheticsIndices({
useAllRemoteClusters: false,
selectedRemoteClusters: ['cluster1', 'cluster2'],
remoteClusters: [
{ name: 'cluster1', isConnected: false },
{ name: 'cluster2', isConnected: false },
],
});
expect(result).toStrictEqual([SYNTHETICS_INDEX_PATTERN]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { SYNTHETICS_INDEX_PATTERN } from './constants';

interface Props {
useAllRemoteClusters: boolean;
selectedRemoteClusters: string[];
remoteClusters?: Array<{ name: string; isConnected: boolean }>;
}

/**
* @returns the local synthetics index or the remote cluster indices based on the CCS settings.
* If `useAllRemoteClusters` is false and no remote clusters are selected, returns only the local index.
* If `useAllRemoteClusters` is true, returns both the local index and a wildcard remote index.
* If specific clusters are selected, returns the local index plus indices for connected selected clusters.
*/
export const getSyntheticsIndices = ({
useAllRemoteClusters,
selectedRemoteClusters,
remoteClusters = [],
}: Props): string[] => {
if (!useAllRemoteClusters && selectedRemoteClusters.length === 0) {
return [SYNTHETICS_INDEX_PATTERN];
}

if (useAllRemoteClusters) {
return [SYNTHETICS_INDEX_PATTERN, `*:${SYNTHETICS_INDEX_PATTERN}`];
}

return remoteClusters.reduce(
(acc, { name, isConnected }) => {
if (isConnected && selectedRemoteClusters.includes(name)) {
acc.push(`${name}:${SYNTHETICS_INDEX_PATTERN}`);
}
return acc;
},
[SYNTHETICS_INDEX_PATTERN] as string[]
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 * as t from 'io-ts';

export const remoteSyntheticsClusterSchema = t.type({
name: t.string,
isConnected: t.boolean,
});

export const syntheticsCCSSettingsSchema = t.type({
useAllRemoteClusters: t.boolean,
selectedRemoteClusters: t.array(t.string),
remoteKibanaUrls: t.record(t.string, t.string),
});

export type RemoteSyntheticsCluster = t.TypeOf<typeof remoteSyntheticsClusterSchema>;
export type SyntheticsCCSSettings = t.TypeOf<typeof syntheticsCCSSettingsSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export * from './network_events';
export * from './monitor_management';
export * from './monitor_management/synthetics_private_locations';
export * from './monitor_health';
export * from './ccs_settings';
export * from './remote';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as t from 'io-ts';
import { ObserverCodec } from '../ping/observer';
import { ErrorStateCodec } from '../ping/error_state';
import { AgentType, MonitorType, PingErrorType, UrlType } from '..';
import { remoteMonitorInfoSchema } from '../remote';

export const OverviewPingCodec = t.intersection([
t.interface({
Expand Down Expand Up @@ -55,6 +56,7 @@ export const OverviewStatusMetaDataCodec = t.intersection([
spaces: t.array(t.string),
urls: t.string,
maintenanceWindows: t.array(t.string),
remote: remoteMonitorInfoSchema,
}),
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ObserverCodec } from './observer';
import { ErrorStateCodec } from './error_state';
import { DateRangeType } from '../common';
import { SyntheticsDataType } from './synthetics';
import { remoteMonitorInfoSchema } from '../remote';

// IO type for validation
export const PingErrorType = t.intersection([
Expand Down Expand Up @@ -239,6 +240,7 @@ export const PingType = t.intersection([
dataset: t.string,
}),
labels: t.record(t.string, t.string),
remote: remoteMonitorInfoSchema,
}),
]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 * as t from 'io-ts';

export const remoteMonitorInfoSchema = t.type({
remoteName: t.string,
kibanaUrl: t.string,
});

export type RemoteMonitorInfo = t.TypeOf<typeof remoteMonitorInfoSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ import { useRouteMatch } from 'react-router-dom';
import type { EuiPageHeaderProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SYNTHETICS_SETTINGS_ROUTE } from '../../../../../common/constants';
import { useSyntheticsSettingsContext } from '../../contexts';

export type SettingsTabId =
| 'data-retention'
| 'params'
| 'alerting'
| 'private-locations'
| 'api-keys'
| 'advanced';
| 'advanced'
| 'remote-clusters';

export const getSettingsPageHeader = (
history: ReturnType<typeof useHistory>,
syntheticsPath: string
): EuiPageHeaderProps => {
// Not a component, but it doesn't matter. Hooks are just functions
const match = useRouteMatch<{ tabId: SettingsTabId }>(SYNTHETICS_SETTINGS_ROUTE); // eslint-disable-line react-hooks/rules-of-hooks
const { isServerless, isCCSEnabled } = useSyntheticsSettingsContext(); // eslint-disable-line react-hooks/rules-of-hooks

if (!match) {
return {};
Expand Down Expand Up @@ -84,6 +87,17 @@ export const getSettingsPageHeader = (
isSelected: tabId === 'advanced',
href: replaceTab('advanced'),
},
...(!isServerless && isCCSEnabled
? [
{
label: i18n.translate('xpack.synthetics.settingsTabs.remoteClusters', {
defaultMessage: 'Remote Clusters',
}),
isSelected: tabId === 'remote-clusters',
href: replaceTab('remote-clusters'),
},
]
: []),
],
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 { useContext } from 'react';
import { useFetcher } from '@kbn/observability-shared-plugin/public';
import type { SyntheticsCCSSettings } from '../../../../../../../common/runtime_types';
import { SYNTHETICS_API_URLS } from '../../../../../../../common/constants';
import { apiService } from '../../../../../../utils/api_service';
import { SyntheticsRefreshContext } from '../../../../contexts';

export const DEFAULT_CCS_SETTINGS: SyntheticsCCSSettings = {
useAllRemoteClusters: false,
selectedRemoteClusters: [],
remoteKibanaUrls: {},
};

const fetchCCSSettings = async () => {
try {
return await apiService.get<SyntheticsCCSSettings>(SYNTHETICS_API_URLS.CCS_SETTINGS);
} catch (e) {
return DEFAULT_CCS_SETTINGS;
}
};

export const useGetCCSSettings = () => {
const { lastRefresh } = useContext(SyntheticsRefreshContext);

const { data, error, loading } = useFetcher(fetchCCSSettings, [lastRefresh]);

return {
data: data ?? DEFAULT_CCS_SETTINGS,
error,
loading,
};
};
Loading
Loading