Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
revert remoteKibanaUrls changes
  • Loading branch information
mgiota committed Apr 15, 2026
commit 286923933647237b82702c207baf09ce4640f23f
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export const remoteSyntheticsClusterSchema = t.type({
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>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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 @@ -15,7 +15,6 @@ import { SyntheticsRefreshContext } from '../../../../contexts';
export const DEFAULT_CCS_SETTINGS: SyntheticsCCSSettings = {
useAllRemoteClusters: false,
selectedRemoteClusters: [],
remoteKibanaUrls: {},
};

const fetchCCSSettings = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const mockSaveSettings = jest.fn();
const mockCCSSettingsData = {
useAllRemoteClusters: false,
selectedRemoteClusters: [] as string[],
remoteKibanaUrls: {} as Record<string, string>,
};

jest.mock('./hooks/use_get_ccs_settings', () => ({
Expand Down Expand Up @@ -95,34 +94,6 @@ describe('<RemoteClustersForm />', () => {
expect(comboBox).toHaveAttribute('disabled');
});

it('shows the Kibana URL table when clusters are selected', () => {
jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({
data: mockRemoteClusters,
status: observabilitySharedPublic.FETCH_STATUS.SUCCESS,
refetch: () => {},
});

// Toggle "use all" to show URLs for all clusters
render(<RemoteClustersForm />);
const toggle = screen.getByTestId('syntheticsUseAllRemoteClusters');
fireEvent.click(toggle);

expect(screen.getByText('cluster-a')).toBeInTheDocument();
expect(screen.getByText('cluster-b')).toBeInTheDocument();
expect(screen.getByTestId('syntheticsRemoteClusterUrl-cluster-a')).toBeInTheDocument();
expect(screen.getByTestId('syntheticsRemoteClusterUrl-cluster-b')).toBeInTheDocument();
});

it('shows connection status for remote clusters', () => {
renderWithClusters();

const toggle = screen.getByTestId('syntheticsUseAllRemoteClusters');
fireEvent.click(toggle);

expect(screen.getByText('Connected')).toBeInTheDocument();
expect(screen.getByText('Disconnected')).toBeInTheDocument();
});

it('enables the Apply button only when form is dirty', () => {
renderWithClusters();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiBasicTable,
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiComboBox,
EuiDescribedFormGroup,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
Expand All @@ -23,7 +21,7 @@ import {
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
import type { EuiBasicTableColumn, EuiComboBoxOptionOption } from '@elastic/eui';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
import { useFetcher } from '@kbn/observability-shared-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { isEqual } from 'lodash';
Expand All @@ -37,12 +35,6 @@ interface RemoteCluster {
isConnected: boolean;
}

interface ClusterUrlRow {
clusterName: string;
isConnected: boolean;
kibanaUrl: string;
}

export const RemoteClustersForm = () => {
const { http } = useKibana().services;
const { isServerless, isCCSEnabled } = useSyntheticsSettingsContext();
Expand All @@ -69,14 +61,12 @@ export const RemoteClustersForm = () => {
// Local form state
const [useAllRemoteClusters, setUseAllRemoteClusters] = useState(false);
const [selectedRemoteClusters, setSelectedRemoteClusters] = useState<string[]>([]);
const [remoteKibanaUrls, setRemoteKibanaUrls] = useState<Record<string, string>>({});

// Sync local state from saved settings when loaded
useEffect(() => {
if (savedSettings) {
setUseAllRemoteClusters(savedSettings.useAllRemoteClusters);
setSelectedRemoteClusters(savedSettings.selectedRemoteClusters);
setRemoteKibanaUrls(savedSettings.remoteKibanaUrls);
}
}, [savedSettings]);

Expand All @@ -90,9 +80,8 @@ export const RemoteClustersForm = () => {
() => ({
useAllRemoteClusters,
selectedRemoteClusters,
remoteKibanaUrls,
}),
[useAllRemoteClusters, selectedRemoteClusters, remoteKibanaUrls]
[useAllRemoteClusters, selectedRemoteClusters]
);

const isFormDirty = !isEqual(currentFormValues, savedSettings ?? DEFAULT_CCS_SETTINGS);
Expand All @@ -101,42 +90,13 @@ export const RemoteClustersForm = () => {
if (savedSettings) {
setUseAllRemoteClusters(savedSettings.useAllRemoteClusters);
setSelectedRemoteClusters(savedSettings.selectedRemoteClusters);
setRemoteKibanaUrls(savedSettings.remoteKibanaUrls);
}
}, [savedSettings]);

const handleSave = useCallback(async () => {
await saveSettings(currentFormValues);
}, [saveSettings, currentFormValues]);

// Determine which clusters to show in the Kibana URL table
const clustersForUrlTable: ClusterUrlRow[] = useMemo(() => {
const clusterList = remoteClusters ?? [];

let relevantClusters: RemoteCluster[];
if (useAllRemoteClusters) {
relevantClusters = clusterList;
} else {
relevantClusters = selectedRemoteClusters.map((name) => {
const found = clusterList.find((c) => c.name === name);
return found ?? { name, isConnected: false };
});
}

return relevantClusters.map((cluster) => ({
clusterName: cluster.name,
isConnected: cluster.isConnected,
kibanaUrl: remoteKibanaUrls[cluster.name] ?? '',
}));
}, [remoteClusters, useAllRemoteClusters, selectedRemoteClusters, remoteKibanaUrls]);

const handleUrlChange = useCallback((clusterName: string, url: string) => {
setRemoteKibanaUrls((prev) => ({
...prev,
[clusterName]: url,
}));
}, []);

// Combo box options for cluster selection
const clusterOptions: EuiComboBoxOptionOption[] = useMemo(() => {
return (remoteClusters ?? []).map((cluster) => ({
Expand All @@ -154,51 +114,11 @@ export const RemoteClustersForm = () => {
return selectedRemoteClusters.map((name) => ({ label: name, value: name }));
}, [selectedRemoteClusters]);

// Table columns for Kibana URL mapping
const urlTableColumns: Array<EuiBasicTableColumn<ClusterUrlRow>> = useMemo(
() => [
{
field: 'clusterName',
name: CLUSTER_NAME_LABEL,
width: '30%',
truncateText: false,
},
{
field: 'isConnected',
name: STATUS_LABEL,
width: '20%',
render: (isConnected: boolean) =>
isConnected ? (
<EuiHealth color="success">{CONNECTED_LABEL}</EuiHealth>
) : (
<EuiHealth color="danger">{DISCONNECTED_LABEL}</EuiHealth>
),
},
{
field: 'kibanaUrl',
name: KIBANA_URL_LABEL,
width: '50%',
render: (_: string, row: ClusterUrlRow) => (
<EuiFieldText
data-test-subj={`syntheticsRemoteClusterUrl-${row.clusterName}`}
placeholder="https://..."
value={row.kibanaUrl}
onChange={(e) => handleUrlChange(row.clusterName, e.target.value)}
disabled={!canEdit}
compressed
/>
),
},
],
[handleUrlChange, canEdit]
);

if (isServerless || !isCCSEnabled) {
return null;
}

const hasNoClusters = !loading && (remoteClusters ?? []).length === 0;
const hasSelectedClusters = useAllRemoteClusters || selectedRemoteClusters.length > 0;

return (
<EuiForm>
Expand Down Expand Up @@ -255,22 +175,6 @@ export const RemoteClustersForm = () => {
</EuiFormRow>
</EuiDescribedFormGroup>

{hasSelectedClusters && clustersForUrlTable.length > 0 && (
<>
<EuiSpacer size="l" />
<h4>{KIBANA_URLS_TITLE}</h4>
<EuiSpacer size="s" />
<p>{KIBANA_URLS_DESCRIPTION}</p>
<EuiSpacer size="m" />
<EuiBasicTable
data-test-subj="syntheticsRemoteClusterUrlTable"
items={clustersForUrlTable}
columns={urlTableColumns}
rowHeader="clusterName"
/>
</>
)}

<EuiSpacer />
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
Expand Down Expand Up @@ -338,30 +242,6 @@ const SELECT_CLUSTERS_PLACEHOLDER = i18n.translate(
{ defaultMessage: 'Search for remote clusters' }
);

const KIBANA_URLS_TITLE = i18n.translate('xpack.synthetics.settings.ccs.kibanaUrlsTitle', {
defaultMessage: 'Kibana URLs',
});

const KIBANA_URLS_DESCRIPTION = i18n.translate(
'xpack.synthetics.settings.ccs.kibanaUrlsDescription',
{
defaultMessage:
'Configure the Kibana URL for each remote cluster. These URLs are used to create deep links to remote monitor details.',
}
);

const CLUSTER_NAME_LABEL = i18n.translate('xpack.synthetics.settings.ccs.clusterName', {
defaultMessage: 'Cluster',
});

const STATUS_LABEL = i18n.translate('xpack.synthetics.settings.ccs.status', {
defaultMessage: 'Status',
});

const KIBANA_URL_LABEL = i18n.translate('xpack.synthetics.settings.ccs.kibanaUrl', {
defaultMessage: 'Kibana URL',
});

const CONNECTED_LABEL = i18n.translate('xpack.synthetics.settings.ccs.connected', {
defaultMessage: 'Connected',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const DYNAMIC_SETTINGS_DEFAULTS: DynamicSettingsAttributes = {
},
useAllRemoteClusters: false,
selectedRemoteClusters: [],
remoteKibanaUrls: {},
};

export const DYNAMIC_SETTINGS_DEFAULT_ATTRIBUTES: DynamicSettingsAttributes =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,19 @@ describe('remote_result_utils', () => {
});

describe('getRemoteMonitorInfo', () => {
const remoteKibanaUrls: Record<string, string> = {
cluster1: 'https://cluster1.example.com',
cluster2: 'https://cluster2.example.com',
};

it('returns remote info with kibanaUrl for a known remote cluster', () => {
expect(getRemoteMonitorInfo('cluster1:synthetics-browser-default', remoteKibanaUrls)).toEqual(
{
remoteName: 'cluster1',
kibanaUrl: 'https://cluster1.example.com',
}
);
});

it('returns remote info with empty kibanaUrl for an unknown remote cluster', () => {
expect(
getRemoteMonitorInfo('unknown-cluster:synthetics-browser-default', remoteKibanaUrls)
).toEqual({
remoteName: 'unknown-cluster',
kibanaUrl: '',
it('returns remote info for a known remote cluster', () => {
expect(getRemoteMonitorInfo('cluster1:synthetics-browser-default')).toEqual({
remoteName: 'cluster1',
});
});

it('returns undefined for a local index', () => {
expect(getRemoteMonitorInfo('synthetics-browser-default', remoteKibanaUrls)).toBeUndefined();
expect(getRemoteMonitorInfo('synthetics-browser-default')).toBeUndefined();
});

it('returns remote info with empty kibanaUrl when remoteKibanaUrls is empty', () => {
expect(getRemoteMonitorInfo('cluster1:synthetics-browser-default', {})).toEqual({
remoteName: 'cluster1',
kibanaUrl: '',
it('returns remote info for a cluster with hyphens', () => {
expect(getRemoteMonitorInfo('my-remote-cluster:synthetics-http-default')).toEqual({
remoteName: 'my-remote-cluster',
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,14 @@ export function getRemoteClusterName(index: string): string | undefined {
* Returns undefined for local hits.
*
* @param index - The `_index` field from the ES search hit
* @param remoteKibanaUrls - Map of remoteName → kibanaUrl from CCS settings
*/
export function getRemoteMonitorInfo(
index: string,
remoteKibanaUrls: Record<string, string>
): RemoteMonitorInfo | undefined {
export function getRemoteMonitorInfo(index: string): RemoteMonitorInfo | undefined {
const remoteName = getRemoteClusterName(index);
if (!remoteName) {
return undefined;
}

return {
remoteName,
kibanaUrl: remoteKibanaUrls[remoteName] ?? '',
};
}
Loading
Loading