diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx
index 6f6f4533bb7dc..6c64c579e3937 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx
@@ -8,13 +8,7 @@ import React from 'react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
-export const ReadOnlyCallout = ({
- projectId,
- canUsePublicLocations,
-}: {
- projectId?: string;
- canUsePublicLocations?: boolean;
-}) => {
+export const ReadOnlyCallout = ({ projectId }: { projectId?: string }) => {
if (projectId) {
return (
<>
@@ -40,30 +34,5 @@ export const ReadOnlyCallout = ({
);
}
- if (!canUsePublicLocations) {
- return (
- <>
-
- }
- iconType="alert"
- >
-
-
-
-
-
- >
- );
- }
-
return null;
};
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/create_monitor_button.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/create_monitor_button.tsx
index dd3556bdf3754..8baf0f2842918 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/create_monitor_button.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/create_monitor_button.tsx
@@ -17,9 +17,7 @@ import { SyntheticsSettingsContext } from '../../contexts/synthetics_settings_co
export const CreateMonitorButton: React.FC = () => {
const { basePath } = useContext(SyntheticsSettingsContext);
- const {
- enablement: { isEnabled },
- } = useEnablement();
+ const { isEnabled, isServiceAllowed } = useEnablement();
const canEditSynthetics = useCanEditSynthetics();
@@ -31,7 +29,7 @@ export const CreateMonitorButton: React.FC = () => {
iconSide="left"
iconType="plusInCircleFilled"
href={`${basePath}/app/synthetics${MONITOR_ADD_ROUTE}`}
- isDisabled={!isEnabled || !canEditSynthetics}
+ isDisabled={!isEnabled || !canEditSynthetics || !isServiceAllowed}
data-test-subj="syntheticsAddMonitorBtn"
>
{
@@ -14,6 +15,8 @@ export const useCanUsePublicLocById = (configId: string) => {
data: { monitors },
} = useSelector(selectOverviewState);
+ const { isServiceAllowed } = useEnablement();
+
const hasManagedLocation = monitors?.filter(
(mon) => mon.configId === configId && mon.location.isServiceManaged
);
@@ -21,5 +24,9 @@ export const useCanUsePublicLocById = (configId: string) => {
const canUsePublicLocations =
useKibana().services?.application?.capabilities.uptime.elasticManagedLocationsEnabled ?? true;
+ if (!isServiceAllowed) {
+ return false;
+ }
+
return hasManagedLocation ? !!canUsePublicLocations : true;
};
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx
index 885f44eaa5ae1..96d5a50c311e4 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx
@@ -7,35 +7,62 @@
import React from 'react';
import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import * as labels from './labels';
import { useEnablement } from '../../../hooks';
-export const DisabledCallout = ({ total }: { total: number }) => {
- const { enablement, invalidApiKeyError, loading } = useEnablement();
+export const DisabledCallout = ({ total }: { total?: number }) => {
+ const { isEnabled, invalidApiKeyError, loading, isServiceAllowed, canEnable } = useEnablement();
- const showDisableCallout = !enablement.isEnabled && total > 0;
- const showInvalidApiKeyCallout = invalidApiKeyError && total > 0;
+ const showDisableCallout = !isEnabled && total && total > 0;
+ const showInvalidApiKeyCallout = invalidApiKeyError && total && total > 0;
- if (!showDisableCallout && !showInvalidApiKeyCallout) {
+ if ((!showDisableCallout && !showInvalidApiKeyCallout && isServiceAllowed) || loading) {
return null;
}
- return !enablement.canEnable && !loading ? (
+ const disabledCallout =
+ !canEnable && showDisableCallout && !loading ? (
+ <>
+
+ {labels.CALLOUT_MANAGEMENT_DESCRIPTION}
+
+ {labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
+
+ {labels.LEARN_MORE_LABEL}
+
+
+
+
+ >
+ ) : null;
+
+ const disAllowedCallout = !isServiceAllowed ? (
<>
-
- {labels.CALLOUT_MANAGEMENT_DESCRIPTION}
-
- {labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
-
- {labels.LEARN_MORE_LABEL}
-
-
+
+ {SERVICE_NOT_ALLOWED}
>
) : null;
+
+ return (
+ <>
+ {disabledCallout}
+ {disAllowedCallout}
+ >
+ );
};
+
+export const SERVICE_NOT_ALLOWED = i18n.translate('xpack.synthetics.synthetics.serviceNotAllowed', {
+ defaultMessage:
+ 'Account is blocked from using Elastic managed locations, please contact support. Your monitors have been paused.',
+});
+
+const ACCOUNT_BLOCKED = i18n.translate('xpack.synthetics.synthetics.accountBlocked', {
+ defaultMessage: 'Account is blocked.',
+});
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx
index 684fb9fad8036..e7600777ca9ba 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx
@@ -11,6 +11,7 @@ import React from 'react';
import { useHistory } from 'react-router-dom';
import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { useEnablement } from '../../../../hooks';
import { useCanEditSynthetics } from '../../../../../../hooks/use_capabilities';
import {
isStatusEnabled,
@@ -50,6 +51,8 @@ export function useMonitorListColumns({
const history = useHistory();
const canEditSynthetics = useCanEditSynthetics();
+ const { isServiceAllowed } = useEnablement();
+
const { alertStatus, updateAlertEnabledState } = useMonitorAlertEnable();
const isActionLoading = (fields: EncryptedSyntheticsSavedMonitor) => {
@@ -187,7 +190,10 @@ export function useMonitorListColumns({
icon: 'pencil' as const,
type: 'icon' as const,
enabled: (fields) =>
- canEditSynthetics && !isActionLoading(fields) && isPublicLocationsAllowed(fields),
+ canEditSynthetics &&
+ !isActionLoading(fields) &&
+ isPublicLocationsAllowed(fields) &&
+ isServiceAllowed,
onClick: (fields) => {
history.push({
pathname: `/edit-monitor/${fields[ConfigKey.CONFIG_ID]}`,
@@ -226,7 +232,10 @@ export function useMonitorListColumns({
type: 'icon' as const,
color: 'danger' as const,
enabled: (fields) =>
- canEditSynthetics && !isActionLoading(fields) && isPublicLocationsAllowed(fields),
+ canEditSynthetics &&
+ !isActionLoading(fields) &&
+ isPublicLocationsAllowed(fields) &&
+ isServiceAllowed,
onClick: (fields) => {
updateAlertEnabledState({
monitor: {
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx
index 29555c0cf1e90..0814ab1074494 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_enabled.tsx
@@ -14,7 +14,7 @@ import {
useCanEditSynthetics,
useCanUsePublicLocations,
} from '../../../../../../hooks/use_capabilities';
-import { useMonitorEnableHandler } from '../../../../hooks';
+import { useEnablement, useMonitorEnableHandler } from '../../../../hooks';
import { NoPermissionsTooltip } from '../../../common/components/permissions';
import * as labels from './labels';
@@ -37,6 +37,8 @@ export const MonitorEnabled = ({
const canUsePublicLocations = useCanUsePublicLocations(monitor?.[ConfigKey.LOCATIONS]);
+ const { isServiceAllowed } = useEnablement();
+
const monitorName = monitor[ConfigKey.NAME];
const statusLabels = useMemo(() => {
return {
@@ -75,7 +77,9 @@ export const MonitorEnabled = ({
{
- const { enablement, loading } = useEnablement();
- const { isEnabled } = enablement;
+ const { isEnabled, loading } = useEnablement();
return !isEnabled && !loading ? (
{
+export const MonitorManagementPage: React.FC = () => {
useTrackPageview({ app: 'synthetics', path: 'monitors' });
useTrackPageview({ app: 'synthetics', path: 'monitors', delay: 15000 });
useMonitorListBreadcrumbs();
- const {
- error: enablementError,
- enablement: { isEnabled },
- loading: enablementLoading,
- } = useEnablement();
+ const { error: enablementError, isEnabled, loading: enablementLoading } = useEnablement();
useOverviewStatus({ scopeStatusByLocation: false });
@@ -65,9 +60,3 @@ const MonitorManagementPage: React.FC = () => {
>
);
};
-
-export const MonitorsPageWithServiceAllowed = React.memo(() => (
-
-
-
-));
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.test.tsx
index ea8edd7878a40..6ff6118ad29db 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.test.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.test.tsx
@@ -12,6 +12,7 @@ import { ActionsPopover } from './actions_popover';
import * as editMonitorLocatorModule from '../../../../hooks/use_edit_monitor_locator';
import * as monitorDetailLocatorModule from '../../../../hooks/use_monitor_detail_locator';
import * as monitorEnableHandlerModule from '../../../../hooks/use_monitor_enable_handler';
+import * as enablementHook from '../../../../hooks/use_enablement';
import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public';
import { MonitorOverviewItem } from '../types';
@@ -19,6 +20,17 @@ describe('ActionsPopover', () => {
let testMonitor: MonitorOverviewItem;
beforeEach(() => {
+ jest.spyOn(enablementHook, 'useEnablement').mockReturnValue({
+ isServiceAllowed: true,
+ areApiKeysEnabled: true,
+ canManageApiKeys: true,
+ canEnable: true,
+ isEnabled: true,
+ invalidApiKeyError: false,
+ loading: false,
+ error: null,
+ });
+
testMonitor = {
location: {
id: 'us_central',
@@ -106,6 +118,7 @@ describe('ActionsPopover', () => {
locationId={testMonitor.location.id}
/>
);
+
expect(getByRole('link')?.getAttribute('href')).toBe('/a/test/edit/url');
});
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx
index 55dcdb3d06341..dc65e36ed18d7 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx
@@ -29,7 +29,7 @@ import {
import { useMonitorAlertEnable } from '../../../../hooks/use_monitor_alert_enable';
import { ConfigKey, MonitorOverviewItem } from '../../../../../../../common/runtime_types';
import { useCanEditSynthetics } from '../../../../../../hooks/use_capabilities';
-import { useMonitorEnableHandler, useLocationName } from '../../../../hooks';
+import { useMonitorEnableHandler, useLocationName, useEnablement } from '../../../../hooks';
import { setFlyoutConfig } from '../../../../state/overview/actions';
import { useEditMonitorLocator } from '../../../../hooks/use_edit_monitor_locator';
import { useMonitorDetailLocator } from '../../../../hooks/use_monitor_detail_locator';
@@ -116,6 +116,8 @@ export function ActionsPopover({
const canUsePublicLocations = useCanUsePublicLocById(monitor.configId);
+ const { isServiceAllowed } = useEnablement();
+
const labels = useMemo(
() => ({
enabledSuccessLabel: enabledSuccessLabel(monitor.name),
@@ -185,7 +187,7 @@ export function ActionsPopover({
),
icon: 'beaker',
- disabled: testInProgress || !canUsePublicLocations,
+ disabled: testInProgress || !canUsePublicLocations || !isServiceAllowed,
onClick: () => {
dispatch(manualTestMonitorAction.get({ configId: monitor.configId, name: monitor.name }));
dispatch(setFlyoutConfig(null));
@@ -199,7 +201,7 @@ export function ActionsPopover({
),
icon: 'pencil',
- disabled: !canEditSynthetics,
+ disabled: !canEditSynthetics || !isServiceAllowed,
href: editUrl,
},
{
@@ -228,7 +230,7 @@ export function ActionsPopover({
{monitor.isStatusAlertEnabled ? disableAlertLabel : enableMonitorAlertLabel}
),
- disabled: !canEditSynthetics || !canUsePublicLocations,
+ disabled: !canEditSynthetics || !canUsePublicLocations || !isServiceAllowed,
icon: alertLoading ? (
) : monitor.isStatusAlertEnabled ? (
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx
index 7efcb5059432b..e16633fed2444 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx
@@ -52,10 +52,7 @@ export const OverviewPage: React.FC = () => {
}
}, [dispatch, locationsLoaded, locationsLoading]);
- const {
- enablement: { isEnabled },
- loading: enablementLoading,
- } = useEnablement();
+ const { isEnabled, loading: enablementLoading } = useEnablement();
const {
loaded: overviewLoaded,
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx
index 0ac6a3ccc0f7e..e2a7ded0dff64 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx
@@ -14,7 +14,7 @@ import { RefreshButton } from '../common/components/refresh_button';
import { OverviewPage } from './overview/overview_page';
import { MonitorsPageHeader } from './management/page_header/monitors_page_header';
import { CreateMonitorButton } from './create_monitor_button';
-import { MonitorsPageWithServiceAllowed } from './monitors_page';
+import { MonitorManagementPage } from './monitors_page';
import { RouteProps } from '../../routes';
import { MONITORS_ROUTE, OVERVIEW_ROUTE } from '../../../../../common/constants';
@@ -48,7 +48,7 @@ export const getMonitorsRoute = (
values: { baseTitle },
}),
path: MONITORS_ROUTE,
- component: MonitorsPageWithServiceAllowed,
+ component: MonitorManagementPage,
dataTestSubj: 'syntheticsMonitorManagementPage',
pageHeader: {
...sharedProps,
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx
index ab599de31c0c1..24c32569f1f2a 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx
@@ -35,6 +35,16 @@ describe('', () => {
onDelete: jest.fn(),
deleteLoading: false,
});
+ jest.spyOn(permissionsHooks, 'useEnablement').mockReturnValue({
+ isServiceAllowed: true,
+ areApiKeysEnabled: true,
+ canManageApiKeys: true,
+ canEnable: true,
+ isEnabled: true,
+ invalidApiKeyError: false,
+ loading: false,
+ error: null,
+ });
});
it.each([true, false])(
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/project_api_keys/project_api_keys.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/project_api_keys/project_api_keys.tsx
index e03ce1879199e..5ea801dea4ed3 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/project_api_keys/project_api_keys.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/project_api_keys/project_api_keys.tsx
@@ -21,10 +21,7 @@ const syntheticsTestRunDocsLink =
'https://www.elastic.co/guide/en/observability/current/synthetic-run-tests.html';
export const ProjectAPIKeys = () => {
- const {
- loading: enablementLoading,
- enablement: { canManageApiKeys },
- } = useEnablement();
+ const { loading: enablementLoading, canManageApiKeys } = useEnablement();
const [apiKey, setApiKey] = useState(undefined);
const [loadAPIKey, setLoadAPIKey] = useState(false);
const [accessToElasticManagedLocations, setAccessToElasticManagedLocations] = useState(true);
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/index.ts
index 6f95e839dcda6..2e5d9e2f76d66 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/index.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/index.ts
@@ -8,7 +8,6 @@
export * from './use_absolute_date';
export * from './use_url_params';
export * from './use_breadcrumbs';
-export * from './use_service_allowed';
export * from './use_enablement';
export * from './use_locations';
export * from './use_last_x_checks';
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_enablement.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_enablement.ts
index 99b34ec3b86e5..7032e6c9cb4d1 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_enablement.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_enablement.ts
@@ -32,12 +32,11 @@ export function useEnablement() {
}, [application, enablement, loading]);
return {
- enablement: {
- areApiKeysEnabled: enablement?.areApiKeysEnabled,
- canManageApiKeys: enablement?.canManageApiKeys,
- canEnable: enablement?.canEnable,
- isEnabled: enablement?.isEnabled,
- },
+ areApiKeysEnabled: enablement?.areApiKeysEnabled,
+ canManageApiKeys: enablement?.canManageApiKeys,
+ canEnable: enablement?.canEnable,
+ isEnabled: enablement?.isEnabled,
+ isServiceAllowed: Boolean(enablement?.isServiceAllowed),
invalidApiKeyError: enablement ? !Boolean(enablement?.isValidApiKey) : false,
error,
loading,
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts
deleted file mode 100644
index 2e2e0f1254acf..0000000000000
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_service_allowed.ts
+++ /dev/null
@@ -1,23 +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 { useDispatch } from 'react-redux';
-import { useEffect } from 'react';
-// import { syntheticsServiceAllowedSelector } from '../../../state/selectors';
-// import { getSyntheticsServiceAllowed } from '../../../state/actions';
-
-export const useSyntheticsServiceAllowed = () => {
- const dispatch = useDispatch();
-
- useEffect(() => {
- // dispatch(getSyntheticsServiceAllowed.get());
- }, [dispatch]);
-
- // return useSelector(syntheticsServiceAllowedSelector);
- // TODO Implement for Synthetics App
- return { isAllowed: true, signupUrl: undefined, loading: false };
-};
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/routes.tsx
index 69e077ae82e7e..ca1d194994e8d 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/routes.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/routes.tsx
@@ -29,8 +29,8 @@ import { getStepDetailsRoute } from './components/step_details_page/route_config
import { getTestRunDetailsRoute } from './components/test_run_details/route_config';
import { getSettingsRouteConfig } from './components/settings/route_config';
import { TestRunDetails } from './components/test_run_details/test_run_details';
-import { MonitorAddPageWithServiceAllowed } from './components/monitor_add_edit/monitor_add_page';
-import { MonitorEditPageWithServiceAllowed } from './components/monitor_add_edit/monitor_edit_page';
+import { MonitorAddPage } from './components/monitor_add_edit/monitor_add_page';
+import { MonitorEditPage } from './components/monitor_add_edit/monitor_edit_page';
import { GettingStartedPage } from './components/getting_started/getting_started_page';
import {
InspectMonitorPortalNode,
@@ -92,7 +92,7 @@ const getRoutes = (
values: { baseTitle },
}),
path: MONITOR_ADD_ROUTE,
- component: MonitorAddPageWithServiceAllowed,
+ component: MonitorAddPage,
dataTestSubj: 'syntheticsMonitorAddPage',
restrictWidth: true,
pageHeader: {
@@ -111,7 +111,7 @@ const getRoutes = (
values: { baseTitle },
}),
path: MONITOR_EDIT_ROUTE,
- component: MonitorEditPageWithServiceAllowed,
+ component: MonitorEditPage,
dataTestSubj: 'syntheticsMonitorEditPage',
restrictWidth: true,
pageHeader: {
diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/enablement.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/enablement.ts
index 4c29cb300d6fa..b78c71e71353f 100644
--- a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/enablement.ts
+++ b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/enablement.ts
@@ -18,8 +18,14 @@ export const getSyntheticsEnablementRoute: SyntheticsRestApiRouteFactory = () =>
path: SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT,
writeAccess: false,
validate: {},
- handler: async ({ savedObjectsClient, request, server }): Promise => {
+ handler: async ({
+ savedObjectsClient,
+ request,
+ server,
+ syntheticsMonitorClient,
+ }): Promise => {
try {
+ const isServiceAllowed = syntheticsMonitorClient.syntheticsService.isAllowed;
const result = await getSyntheticsEnablement({
server,
});
@@ -44,12 +50,14 @@ export const getSyntheticsEnablementRoute: SyntheticsRestApiRouteFactory = () =>
server,
});
} else {
- return result;
+ return { ...result, isServiceAllowed };
}
- return getSyntheticsEnablement({
+ const res = await getSyntheticsEnablement({
server,
});
+
+ return { ...res, isServiceAllowed };
} catch (e) {
server.logger.error(e);
throw e;
diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.test.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.test.ts
index 3c2a019625136..66ef4e9faa0c8 100644
--- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.test.ts
+++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.test.ts
@@ -112,7 +112,7 @@ describe('checkAccountAccessStatus', () => {
it('includes a header with the kibana version', async () => {
const apiClient = new ServiceAPIClient(
jest.fn() as unknown as Logger,
- { tls: { certificate: 'crt', key: 'k' } } as ServiceConfig,
+ { tls: { certificate: 'crt', key: 'k' }, manifestUrl: 'http://localhost' } as ServiceConfig,
{ isDev: false, stackVersion: '8.4', coreStart: mockCoreStart } as SyntheticsServerSetup
);
diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.ts
index f5c996e5efc59..09ad88476ec06 100644
--- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.ts
+++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/service_api_client.ts
@@ -86,7 +86,7 @@ export class ServiceAPIClient {
}
async checkAccountAccessStatus() {
- if (this.authorization) {
+ if (this.authorization || !this.config?.manifestUrl) {
// in case username/password is provided, we assume it's always allowed
return { allowed: true, signupUrl: null };
}
diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts
index e7fcff93c8018..c69cafa41e848 100644
--- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts
+++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts
@@ -140,6 +140,7 @@ describe('SyntheticsService', () => {
service.apiClient.locations = locations;
service.locations = locations;
+ service.isAllowed = true;
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts
index b2cf070b5bb34..35bf0d9dc7ce6 100644
--- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts
+++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts
@@ -78,11 +78,12 @@ export class SyntheticsService {
this.logger = server.logger;
this.server = server;
this.config = server.config.service ?? {};
- this.isAllowed = false;
+
+ // set isAllowed to false if manifestUrl is not set
+ this.isAllowed = this.config.manifestUrl ? false : true;
this.signupUrl = null;
this.apiClient = new ServiceAPIClient(server.logger, this.config, this.server);
-
this.esHosts = getEsHosts({ config: this.config, cloud: server.cloud });
this.locations = [];
@@ -188,7 +189,7 @@ export class SyntheticsService {
await service.pushConfigs();
} else {
if (!service.isAllowed) {
- service.logger.debug(
+ service.logger.error(
'User is not allowed to access Synthetics service. Please contact support.'
);
}
@@ -342,7 +343,7 @@ export class SyntheticsService {
async addConfigs(configs: ConfigData[]) {
try {
- if (configs.length === 0) {
+ if (configs.length === 0 || !this.isAllowed) {
return;
}
@@ -367,7 +368,7 @@ export class SyntheticsService {
async editConfig(monitorConfig: ConfigData[], isEdit = true) {
try {
- if (monitorConfig.length === 0) {
+ if (monitorConfig.length === 0 || !this.isAllowed) {
return;
}
const license = await this.getLicense();
diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/get_snapshot_counts.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/get_snapshot_counts.ts
index b128c77fa5915..d3128caf2f8a8 100644
--- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/get_snapshot_counts.ts
+++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/get_snapshot_counts.ts
@@ -48,12 +48,8 @@ export const getSnapshotCount: UMElasticsearchQueryFn => {
- const { body: res } = await context.search(
- {
- body: statusCountBody(await context.dateAndCustomFilters(), context),
- },
- 'getSnapshotCount'
- );
+ const body = statusCountBody(await context.dateAndCustomFilters(), context);
+ const { body: res } = await context.search(body, 'getSnapshotCount');
return (
(res.aggregations?.counts?.value as Snapshot) ?? {
diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/find_potential_matches.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/find_potential_matches.ts
index 2649205d3b473..7d988ed50c435 100644
--- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/find_potential_matches.ts
+++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/find_potential_matches.ts
@@ -41,11 +41,7 @@ export const findPotentialMatches = async (
const query = async (queryContext: QueryContext, searchAfter: any, size: number) => {
const body = await queryBody(queryContext, searchAfter, size);
- const params = {
- body,
- };
-
- const response = await queryContext.search(params, 'getMonitorList-potentialMatches');
+ const response = await queryContext.search(body, 'getMonitorList-potentialMatches');
return response;
};
diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/query_context.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/query_context.ts
index 1ab9d9ece169f..9f76d8e37b099 100644
--- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/query_context.ts
+++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/query_context.ts
@@ -7,7 +7,7 @@
import moment from 'moment';
import type { ESFilter } from '@kbn/es-types';
-import type { SearchRequest } from '@kbn/data-plugin/common';
+import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
import type { CursorPagination } from './types';
import { CursorDirection, SortOrder } from '../../../../../common/runtime_types';
import { UptimeEsClient } from '../../lib';
diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/refine_potential_matches.ts
index 7956966ec71c5..0b38979676e4e 100644
--- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/refine_potential_matches.ts
+++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/requests/search/refine_potential_matches.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
import { QueryContext } from './query_context';
import { MonitorSummary, Ping } from '../../../../../common/runtime_types';
@@ -115,50 +116,48 @@ export const query = async (
queryContext: QueryContext,
potentialMatchMonitorIDs: string[]
): Promise => {
- const params = {
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- await queryContext.dateRangeFilter(),
- { terms: { 'monitor.id': potentialMatchMonitorIDs } },
- ],
- },
+ const params: SearchRequest = {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ await queryContext.dateRangeFilter(),
+ { terms: { 'monitor.id': potentialMatchMonitorIDs } },
+ ],
},
- aggs: {
- monitor: {
- terms: {
- field: 'monitor.id',
- size: potentialMatchMonitorIDs.length,
- order: { _key: queryContext.cursorOrder() },
- },
- aggs: {
- location: {
- terms: { field: 'observer.geo.name', missing: 'N/A', size: 100 },
- aggs: {
- summaries: {
- // only match summary docs because we only want the latest *complete* check group.
- filter: { exists: { field: 'summary' } },
- aggs: {
- latest: {
- top_hits: {
- sort: [{ '@timestamp': 'desc' }],
- size: 1,
- },
+ },
+ aggs: {
+ monitor: {
+ terms: {
+ field: 'monitor.id',
+ size: potentialMatchMonitorIDs.length,
+ order: { _key: queryContext.cursorOrder() },
+ },
+ aggs: {
+ location: {
+ terms: { field: 'observer.geo.name', missing: 'N/A', size: 100 },
+ aggs: {
+ summaries: {
+ // only match summary docs because we only want the latest *complete* check group.
+ filter: { exists: { field: 'summary' } },
+ aggs: {
+ latest: {
+ top_hits: {
+ sort: [{ '@timestamp': 'desc' }],
+ size: 1,
},
},
},
- // We want to find the latest check group, even if it's not part of a summary
- latest_matching: {
- filter: queryContext.filterClause || { match_all: {} },
- aggs: {
- top: {
- top_hits: {
- _source: ['@timestamp'],
- sort: [{ '@timestamp': 'desc' }],
- size: 1,
- },
+ },
+ // We want to find the latest check group, even if it's not part of a summary
+ latest_matching: {
+ filter: queryContext.filterClause || { match_all: {} },
+ aggs: {
+ top: {
+ top_hits: {
+ _source: ['@timestamp'],
+ sort: [{ '@timestamp': 'desc' }],
+ size: 1,
},
},
},
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx
index 939c2a03547d7..cda7e4e200877 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { noop } from 'lodash/fp';
import { render, screen } from '@testing-library/react';
+import { coreMock } from '@kbn/core/public/mocks';
import { TestProviders } from '../../../../../common/mock';
import { useRuleDetailsContextMock } from '../__mocks__/rule_details_context';
@@ -22,6 +23,8 @@ jest.mock('../../../../../common/containers/sourcerer');
jest.mock('../../../../rule_monitoring/components/execution_results_table/use_execution_results');
jest.mock('../rule_details_context');
+const coreStart = coreMock.createStart();
+
const mockUseSourcererDataView = useSourcererDataView as jest.Mock;
mockUseSourcererDataView.mockReturnValue({
indexPattern: { fields: [] },
@@ -42,7 +45,7 @@ describe('ExecutionLogTable', () => {
test('Shows total events returned', () => {
const ruleDetailsContext = useRuleDetailsContextMock.create();
(useRuleDetailsContext as jest.Mock).mockReturnValue(ruleDetailsContext);
- render(, {
+ render(, {
wrapper: TestProviders,
});
expect(screen.getByTestId('executionsShowing')).toHaveTextContent('Showing 7 rule executions');
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx
index 1171c22fbcea2..7770548075511 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx
@@ -26,7 +26,10 @@ import {
import type { Filter, Query } from '@kbn/es-query';
import { buildFilter, FILTERS } from '@kbn/es-query';
import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules';
-import { mountReactNode } from '@kbn/core-mount-utils-browser-internal';
+import { toMountPoint } from '@kbn/react-kibana-mount';
+import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
+import type { I18nStart } from '@kbn/core-i18n-browser';
+import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import { InputsModelId } from '../../../../../common/store/inputs/constants';
@@ -82,7 +85,13 @@ const DatePickerEuiFlexItem = styled(EuiFlexItem)`
max-width: 582px;
`;
-interface ExecutionLogTableProps {
+interface StartServices {
+ analytics: AnalyticsServiceStart;
+ i18n: I18nStart;
+ theme: ThemeServiceStart;
+}
+
+interface ExecutionLogTableProps extends StartServices {
ruleId: string;
selectAlertsTab: () => void;
}
@@ -96,6 +105,7 @@ interface CachedGlobalQueryState {
const ExecutionLogTableComponent: React.FC = ({
ruleId,
selectAlertsTab,
+ ...startServices
}) => {
const {
docLinks,
@@ -299,7 +309,7 @@ const ExecutionLogTableComponent: React.FC = ({
successToastId.current = addSuccess(
{
title: i18n.ACTIONS_SEARCH_FILTERS_HAVE_BEEN_UPDATED_TITLE,
- text: mountReactNode(
+ text: toMountPoint(
<>
{i18n.ACTIONS_SEARCH_FILTERS_HAVE_BEEN_UPDATED_DESCRIPTION}
@@ -309,7 +319,8 @@ const ExecutionLogTableComponent: React.FC = ({
- >
+ >,
+ startServices
),
},
// Essentially keep toast around till user dismisses via 'x'
@@ -333,6 +344,7 @@ const ExecutionLogTableComponent: React.FC = ({
selectAlertsTab,
timerange,
uuidDataViewField,
+ startServices,
]
);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
index e99e6d6659034..2dacd37f7d245 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
@@ -179,6 +179,9 @@ const RuleDetailsPageComponent: React.FC = ({
clearSelected,
}) => {
const {
+ analytics,
+ i18n: i18nStart,
+ theme,
application: {
navigateToApp,
capabilities: { actions },
@@ -769,7 +772,13 @@ const RuleDetailsPageComponent: React.FC = ({
/>
-
+
diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json
index 68351d1dd48a1..0a74be06da7d0 100644
--- a/x-pack/plugins/security_solution/tsconfig.json
+++ b/x-pack/plugins/security_solution/tsconfig.json
@@ -105,7 +105,6 @@
"@kbn/react-field",
"@kbn/field-types",
"@kbn/securitysolution-rules",
- "@kbn/core-mount-utils-browser-internal",
"@kbn/cases-components",
"@kbn/event-log-plugin",
"@kbn/task-manager-plugin",
@@ -203,6 +202,9 @@
"@kbn/search-types",
"@kbn/field-utils",
"@kbn/core-saved-objects-api-server-mocks",
- "@kbn/langchain"
+ "@kbn/langchain",
+ "@kbn/core-analytics-browser",
+ "@kbn/core-i18n-browser",
+ "@kbn/core-theme-browser"
]
}
diff --git a/x-pack/plugins/spaces/common/types/space/v1.ts b/x-pack/plugins/spaces/common/types/space/v1.ts
index 0d0a32d758da8..e18ad5e656efc 100644
--- a/x-pack/plugins/spaces/common/types/space/v1.ts
+++ b/x-pack/plugins/spaces/common/types/space/v1.ts
@@ -58,6 +58,11 @@ export interface Space {
* @private
*/
_reserved?: boolean;
+
+ /**
+ * Solution selected for this space.
+ */
+ solution?: 'security' | 'observability' | 'search' | 'classic';
}
/**
diff --git a/x-pack/plugins/spaces/server/lib/space_schema.test.ts b/x-pack/plugins/spaces/server/lib/space_schema.test.ts
index d6911a987786e..c31d7f5ca3c22 100644
--- a/x-pack/plugins/spaces/server/lib/space_schema.test.ts
+++ b/x-pack/plugins/spaces/server/lib/space_schema.test.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
-import { spaceSchema } from './space_schema';
+import { getSpaceSchema } from './space_schema';
+
+// non-serverless space schema
+const spaceBaseSchema = getSpaceSchema(false);
+const spaceServerlessSchema = getSpaceSchema(true);
const defaultProperties = {
id: 'foo',
@@ -15,7 +19,7 @@ const defaultProperties = {
describe('#id', () => {
test('is required', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
id: undefined,
})
@@ -26,7 +30,7 @@ describe('#id', () => {
test('allows lowercase a-z, 0-9, "_" and "-"', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
id: 'abcdefghijklmnopqrstuvwxyz0123456789_-',
})
@@ -35,7 +39,7 @@ describe('#id', () => {
test(`doesn't allow uppercase`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
id: 'Foo',
})
@@ -46,7 +50,7 @@ describe('#id', () => {
test(`doesn't allow an empty string`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
id: '',
})
@@ -59,7 +63,7 @@ describe('#id', () => {
(invalidCharacter) => {
test(`doesn't allow ${invalidCharacter}`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
id: `foo-${invalidCharacter}`,
})
@@ -72,7 +76,7 @@ describe('#id', () => {
describe('#disabledFeatures', () => {
test('is optional', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
disabledFeatures: undefined,
})
@@ -80,7 +84,7 @@ describe('#disabledFeatures', () => {
});
test('defaults to an empty array', () => {
- const result = spaceSchema.validate({
+ const result = spaceBaseSchema.validate({
...defaultProperties,
disabledFeatures: undefined,
});
@@ -89,7 +93,7 @@ describe('#disabledFeatures', () => {
test('must be an array if provided', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
disabledFeatures: 'foo',
})
@@ -100,7 +104,7 @@ describe('#disabledFeatures', () => {
test('allows an array of strings', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
disabledFeatures: ['foo', 'bar'],
})
@@ -109,7 +113,7 @@ describe('#disabledFeatures', () => {
test('does not allow an array containing non-string elements', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
disabledFeatures: ['foo', true],
})
@@ -122,7 +126,7 @@ describe('#disabledFeatures', () => {
describe('#color', () => {
test('is optional', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: undefined,
})
@@ -131,7 +135,7 @@ describe('#color', () => {
test(`doesn't allow an empty string`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '',
})
@@ -142,7 +146,7 @@ describe('#color', () => {
test(`allows lower case hex color code`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '#aabbcc',
})
@@ -151,7 +155,7 @@ describe('#color', () => {
test(`allows upper case hex color code`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '#AABBCC',
})
@@ -160,7 +164,7 @@ describe('#color', () => {
test(`allows numeric hex color code`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '#123456',
})
@@ -169,7 +173,7 @@ describe('#color', () => {
test(`must start with a hash`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '123456',
})
@@ -180,7 +184,7 @@ describe('#color', () => {
test(`cannot exceed 6 digits following the hash`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '1234567',
})
@@ -191,7 +195,7 @@ describe('#color', () => {
test(`cannot be fewer than 6 digits following the hash`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
color: '12345',
})
@@ -204,7 +208,7 @@ describe('#color', () => {
describe('#imageUrl', () => {
test('is optional', () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
imageUrl: undefined,
})
@@ -213,7 +217,7 @@ describe('#imageUrl', () => {
test(`must start with data:image`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
imageUrl: 'notValid',
})
@@ -222,7 +226,7 @@ describe('#imageUrl', () => {
test(`checking that a valid image is accepted as imageUrl`, () => {
expect(() =>
- spaceSchema.validate({
+ spaceBaseSchema.validate({
...defaultProperties,
imageUrl:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTnU1rJkAAAB3klEQVRYR+2WzUrDQBCARzwqehE8ir1WPfgqRRA1bePBXgpe/MGCB9/Aiw+j+ASCB6kotklaEwW1F0WwNSaps9lV69awGzBpDzt8pJP9mXxsmk3ABH2oUEIilJAIJSRCCYlQQiKUkIh4QgY5agZodVjBowFrBktWQzDBU2ykiYaDuQpCYgnl3QunGzM6Z6YF+b5SkcgK1UH/aLbYReQiYL9d9/o+XFop5IU0Vl4uapAzoXC3eEBPw9vH1/wT6Vs2otPSkoH/IZzlzO/TU2vgQm8nl69Hp0H7nZ4OXogLJSSKBIUC3w88n+Ueyfv56fVZnqCQNVnCHbLrkV0Gd2d+GNkglsk438dhaTxloZDutV4wb06Vf40JcWZ2sMttPpE8NaHGeBnzIAhwPXqHseVB11EyLD0hxLUeaYud2a3B0g3k7GyFtrhX7F2RqhC+yV3jgTb2Rqdqf7/kUxYiWBOlTtXxfPJEtc8b5thGb+8AhL4ohnCNqQjZ2T2+K5rnw2M6KwEhKNDSGM3pTdxjhDgLbHkw/v/zw4AiPuSsfMzAiTidKxiF/ArpFqyzK8SMOlkwvloUMYRCtNvZLWeuIomd2Za/WZS4QomjhEQoIRFKSIQSEqGERAyfEH4YDBFQ/ARU6BiBxCAIQQAAAABJRU5ErkJggg==',
@@ -230,3 +234,32 @@ describe('#imageUrl', () => {
).not.toThrowError();
});
});
+
+describe('#solution', () => {
+ it('should throw error if solution is defined in serverless offering', () => {
+ expect(() =>
+ spaceServerlessSchema.validate({ ...defaultProperties, solution: 'search' })
+ ).toThrow();
+ });
+
+ it('should not throw error if solution is undefined in classic offering', () => {
+ expect(() =>
+ spaceBaseSchema.validate({ ...defaultProperties, solution: undefined }, {})
+ ).not.toThrow();
+ });
+
+ it('should throw error if solution is invalid in classic offering', () => {
+ expect(() => spaceBaseSchema.validate({ ...defaultProperties, solution: 'some_value' }, {}))
+ .toThrowErrorMatchingInlineSnapshot(`
+ "[solution]: types that failed validation:
+ - [solution.0]: expected value to equal [security]
+ - [solution.1]: expected value to equal [observability]
+ - [solution.2]: expected value to equal [search]
+ - [solution.3]: expected value to equal [classic]"
+ `);
+
+ expect(() =>
+ spaceBaseSchema.validate({ ...defaultProperties, solution: ' search ' }, {})
+ ).toThrow();
+ });
+});
diff --git a/x-pack/plugins/spaces/server/lib/space_schema.ts b/x-pack/plugins/spaces/server/lib/space_schema.ts
index b736994c4d13b..cb184e322b5a9 100644
--- a/x-pack/plugins/spaces/server/lib/space_schema.ts
+++ b/x-pack/plugins/spaces/server/lib/space_schema.ts
@@ -11,7 +11,7 @@ import { MAX_SPACE_INITIALS } from '../../common';
export const SPACE_ID_REGEX = /^[a-z0-9_\-]+$/;
-export const spaceSchema = schema.object({
+const spaceSchema = schema.object({
id: schema.string({
validate: (value) => {
if (!SPACE_ID_REGEX.test(value)) {
@@ -43,3 +43,18 @@ export const spaceSchema = schema.object({
})
),
});
+
+const solutionSchema = schema.oneOf([
+ schema.literal('security'),
+ schema.literal('observability'),
+ schema.literal('search'),
+ schema.literal('classic'),
+]);
+
+export const getSpaceSchema = (isServerless: boolean) => {
+ if (isServerless) {
+ return spaceSchema;
+ }
+
+ return spaceSchema.extends({ solution: schema.maybe(solutionSchema) });
+};
diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts
index 9084a74e24265..a20396c3e4695 100644
--- a/x-pack/plugins/spaces/server/plugin.ts
+++ b/x-pack/plugins/spaces/server/plugin.ts
@@ -125,7 +125,10 @@ export class SpacesPlugin
this.hasOnlyDefaultSpace$ = this.config$.pipe(map(({ maxSpaces }) => maxSpaces === 1));
this.log = initializerContext.logger.get();
this.spacesService = new SpacesService();
- this.spacesClientService = new SpacesClientService((message) => this.log.debug(message));
+ this.spacesClientService = new SpacesClientService(
+ (message) => this.log.debug(message),
+ initializerContext.env.packageInfo.buildFlavor
+ );
}
public setup(core: CoreSetup, plugins: PluginsSetup): SpacesPluginSetup {
@@ -168,16 +171,14 @@ export class SpacesPlugin
const router = core.http.createRouter();
- initExternalSpacesApi(
- {
- router,
- log: this.log,
- getStartServices: core.getStartServices,
- getSpacesService,
- usageStatsServicePromise,
- },
- this.initializerContext.env.packageInfo.buildFlavor
- );
+ initExternalSpacesApi({
+ router,
+ log: this.log,
+ getStartServices: core.getStartServices,
+ getSpacesService,
+ usageStatsServicePromise,
+ isServerless: this.initializerContext.env.packageInfo.buildFlavor === 'serverless',
+ });
initInternalSpacesApi({
router,
diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts
index 7722316799076..8c5782aacd519 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts
@@ -57,7 +57,7 @@ describe('copy to space', () => {
createResolveSavedObjectsImportErrorsMock()
);
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -85,6 +85,7 @@ describe('copy to space', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [[ctsRouteDefinition, ctsRouteHandler], [resolveRouteDefinition, resolveRouteHandler]] =
diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts
index 09ed757a5df74..99ec34917eb5a 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts
@@ -42,7 +42,7 @@ describe('Spaces Public API', () => {
const coreStart = coreMock.createStart();
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -67,6 +67,7 @@ describe('Spaces Public API', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [routeDefinition, routeHandler] = router.delete.mock.calls[0];
diff --git a/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts
index ed7c403182ab5..e6f665f817c55 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts
@@ -42,7 +42,7 @@ describe('_disable_legacy_url_aliases', () => {
const log = loggingSystemMock.create().get('spaces');
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -70,6 +70,7 @@ describe('_disable_legacy_url_aliases', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [routeDefinition, routeHandler] = router.post.mock.calls[0];
diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts
index 34c1ed01e0ce4..938af432b0cfa 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts
@@ -41,7 +41,7 @@ describe('GET space', () => {
const log = loggingSystemMock.create().get('spaces');
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -66,6 +66,7 @@ describe('GET space', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
return {
diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.ts b/x-pack/plugins/spaces/server/routes/api/external/get.ts
index ce89aac5fe186..496c3408b7d2e 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/get.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/get.ts
@@ -30,7 +30,9 @@ export function initGetSpaceApi(deps: ExternalRouteDeps) {
try {
const space = await spacesClient.get(spaceId);
- return response.ok({ body: space });
+ return response.ok({
+ body: space,
+ });
} catch (error) {
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
return response.notFound();
diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts
index aefef389c7d26..a3d4f151c6615 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts
@@ -43,7 +43,7 @@ describe('GET /spaces/space', () => {
const log = loggingSystemMock.create().get('spaces');
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -68,6 +68,7 @@ describe('GET /spaces/space', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
return {
diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_shareable_references.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get_shareable_references.test.ts
index c9ea0d71c11b7..8c9c60f78f461 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/get_shareable_references.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/get_shareable_references.test.ts
@@ -42,7 +42,7 @@ describe('get shareable references', () => {
const { savedObjects, savedObjectsClient } = createMockSavedObjectsService(spaces);
coreStart.savedObjects = savedObjects;
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -66,6 +66,7 @@ describe('get shareable references', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [[getShareableReferences, getShareableReferencesRouteHandler]] = router.post.mock.calls;
diff --git a/x-pack/plugins/spaces/server/routes/api/external/index.ts b/x-pack/plugins/spaces/server/routes/api/external/index.ts
index 290807f9b5f4d..6b919b6898709 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/index.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/index.ts
@@ -4,8 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
-import type { BuildFlavor } from '@kbn/config/src/types';
import type { CoreSetup, Logger } from '@kbn/core/server';
import { initCopyToSpacesApi } from './copy_to_space';
@@ -27,9 +25,10 @@ export interface ExternalRouteDeps {
getSpacesService: () => SpacesServiceStart;
usageStatsServicePromise: Promise;
log: Logger;
+ isServerless: boolean;
}
-export function initExternalSpacesApi(deps: ExternalRouteDeps, buildFlavor: BuildFlavor) {
+export function initExternalSpacesApi(deps: ExternalRouteDeps) {
// These two routes are always registered, internal in serverless by default
initGetSpaceApi(deps);
initGetAllSpacesApi(deps);
@@ -37,7 +36,7 @@ export function initExternalSpacesApi(deps: ExternalRouteDeps, buildFlavor: Buil
// In the serverless environment, Spaces are enabled but are effectively hidden from the user. We
// do not support more than 1 space: the default space. These HTTP APIs for creating, deleting,
// updating, and manipulating saved objects across multiple spaces are not needed.
- if (buildFlavor !== 'serverless') {
+ if (!deps.isServerless) {
initPutSpacesApi(deps);
initDeleteSpacesApi(deps);
initPostSpacesApi(deps);
diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts
index 88c763f31c0be..b36a3619a48e0 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts
@@ -42,7 +42,7 @@ describe('Spaces Public API', () => {
const log = loggingSystemMock.create().get('spaces');
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -67,6 +67,7 @@ describe('Spaces Public API', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [routeDefinition, routeHandler] = router.post.mock.calls[0];
diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.ts b/x-pack/plugins/spaces/server/routes/api/external/post.ts
index d8091a0140e00..34a9c7f854dad 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/post.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/post.ts
@@ -11,17 +11,17 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import type { ExternalRouteDeps } from '.';
import { wrapError } from '../../../lib/errors';
-import { spaceSchema } from '../../../lib/space_schema';
+import { getSpaceSchema } from '../../../lib/space_schema';
import { createLicensedRouteHandler } from '../../lib';
export function initPostSpacesApi(deps: ExternalRouteDeps) {
- const { router, log, getSpacesService } = deps;
+ const { router, log, getSpacesService, isServerless } = deps;
router.post(
{
path: '/api/spaces/space',
validate: {
- body: spaceSchema,
+ body: getSpaceSchema(isServerless),
},
},
createLicensedRouteHandler(async (context, request, response) => {
diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts
index 5e1e6077a758e..2141e369507b9 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts
@@ -42,7 +42,7 @@ describe('PUT /api/spaces/space', () => {
const log = loggingSystemMock.create().get('spaces');
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -67,6 +67,7 @@ describe('PUT /api/spaces/space', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [routeDefinition, routeHandler] = router.put.mock.calls[0];
diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.ts b/x-pack/plugins/spaces/server/routes/api/external/put.ts
index 753ec8e028925..7d0c255392ee4 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/put.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/put.ts
@@ -11,11 +11,11 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import type { ExternalRouteDeps } from '.';
import type { Space } from '../../../../common';
import { wrapError } from '../../../lib/errors';
-import { spaceSchema } from '../../../lib/space_schema';
+import { getSpaceSchema } from '../../../lib/space_schema';
import { createLicensedRouteHandler } from '../../lib';
export function initPutSpacesApi(deps: ExternalRouteDeps) {
- const { router, getSpacesService } = deps;
+ const { router, getSpacesService, isServerless } = deps;
router.put(
{
@@ -24,7 +24,7 @@ export function initPutSpacesApi(deps: ExternalRouteDeps) {
params: schema.object({
id: schema.string(),
}),
- body: spaceSchema,
+ body: getSpaceSchema(isServerless),
},
},
createLicensedRouteHandler(async (context, request, response) => {
diff --git a/x-pack/plugins/spaces/server/routes/api/external/update_objects_spaces.test.ts b/x-pack/plugins/spaces/server/routes/api/external/update_objects_spaces.test.ts
index 195db8148a378..874b5d4284958 100644
--- a/x-pack/plugins/spaces/server/routes/api/external/update_objects_spaces.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/external/update_objects_spaces.test.ts
@@ -43,7 +43,7 @@ describe('update_objects_spaces', () => {
const { savedObjects, savedObjectsClient } = createMockSavedObjectsService(spaces);
coreStart.savedObjects = savedObjects;
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
@@ -67,6 +67,7 @@ describe('update_objects_spaces', () => {
log,
getSpacesService: () => spacesServiceStart,
usageStatsServicePromise,
+ isServerless: false,
});
const [[updateObjectsSpaces, updateObjectsSpacesRouteHandler]] = router.post.mock.calls;
diff --git a/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts b/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts
index 8f316d12209ba..09fdbe7896761 100644
--- a/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/internal/get_content_summary.test.ts
@@ -45,7 +45,7 @@ describe('GET /internal/spaces/{spaceId}/content_summary', () => {
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
- const clientService = new SpacesClientService(jest.fn());
+ const clientService = new SpacesClientService(jest.fn(), 'traditional');
clientService
.setup({ config$: Rx.of(spacesConfig) })
.setClientRepositoryFactory(() => savedObjectsRepositoryMock);
diff --git a/x-pack/plugins/spaces/server/saved_objects/mappings.ts b/x-pack/plugins/spaces/server/saved_objects/mappings.ts
index 90d514e9fd10c..6f4313c3b1ed5 100644
--- a/x-pack/plugins/spaces/server/saved_objects/mappings.ts
+++ b/x-pack/plugins/spaces/server/saved_objects/mappings.ts
@@ -19,6 +19,9 @@ export const SpacesSavedObjectMappings = deepFreeze({
},
},
},
+ solution: {
+ type: 'keyword',
+ },
},
} as const);
diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts
index b86bbf58f065c..1cdfa00d63238 100644
--- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts
+++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { schema } from '@kbn/config-schema';
import type { CoreSetup } from '@kbn/core/server';
import { SpacesSavedObjectMappings, UsageStatsMappings } from './mappings';
@@ -30,6 +31,30 @@ export class SpacesSavedObjectsService {
migrations: {
'6.6.0': spaceMigrations.migrateTo660,
},
+ modelVersions: {
+ 1: {
+ changes: [
+ {
+ type: 'mappings_addition',
+ addedMappings: {
+ solution: { type: 'keyword' },
+ },
+ },
+ ],
+ schemas: {
+ create: SpacesSavedObjectSchemas['8.8.0'].extends({
+ solution: schema.maybe(
+ schema.oneOf([
+ schema.literal('security'),
+ schema.literal('observability'),
+ schema.literal('search'),
+ schema.literal('classic'),
+ ])
+ ),
+ }),
+ },
+ },
+ },
});
core.savedObjects.registerType({
diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts
index 0f689cd492e11..600f6d1aa316c 100644
--- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts
+++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts
@@ -42,6 +42,7 @@ describe('#getAll', () => {
imageUrl: 'go-bots/predates/transformers',
disabledFeatures: [],
_reserved: true,
+ solution: 'search',
bar: 'foo-bar', // an extra attribute that will be ignored during conversion
},
},
@@ -80,6 +81,7 @@ describe('#getAll', () => {
initials: 'FB',
imageUrl: 'go-bots/predates/transformers',
disabledFeatures: [],
+ solution: 'search',
_reserved: true,
},
{
@@ -105,7 +107,13 @@ describe('#getAll', () => {
} as any);
const mockConfig = createMockConfig();
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
const actualSpaces = await client.getAll();
expect(actualSpaces).toEqual(expectedSpaces);
@@ -117,11 +125,46 @@ describe('#getAll', () => {
});
});
+ test('strips solution property in serverless build', async () => {
+ const mockDebugLogger = createMockDebugLogger();
+ const [SOWithSolution] = savedObjects;
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.find.mockResolvedValue({
+ saved_objects: [SOWithSolution],
+ } as any);
+ const mockConfig = createMockConfig();
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'serverless'
+ );
+ const [actualSpace] = await client.getAll();
+ const [{ solution, ...expectedSpace }] = expectedSpaces;
+
+ expect(actualSpace.solution).toBeUndefined();
+ expect(actualSpace).toEqual(expectedSpace);
+ expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({
+ type: 'space',
+ page: 1,
+ perPage: mockConfig.maxSpaces,
+ sortField: 'name.keyword',
+ });
+ });
+
test(`throws Boom.badRequest when an invalid purpose is provided'`, async () => {
const mockDebugLogger = createMockDebugLogger();
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
const mockConfig = createMockConfig();
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
await expect(
client.getAll({ purpose: 'invalid_purpose' as GetAllSpacesPurpose })
).rejects.toThrowErrorMatchingInlineSnapshot(`"unsupported space purpose: invalid_purpose"`);
@@ -162,13 +205,64 @@ describe('#get', () => {
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
mockCallWithRequestRepository.get.mockResolvedValue(savedObject);
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
const id = savedObject.id;
const actualSpace = await client.get(id);
expect(actualSpace).toEqual(expectedSpace);
expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id);
});
+
+ test('strips solution property in serverless build', async () => {
+ const mockDebugLogger = createMockDebugLogger();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.get.mockResolvedValue({
+ ...savedObject,
+ attributes: { ...(savedObject.attributes as Record), solution: 'search' },
+ });
+ const mockConfig = createMockConfig();
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'serverless'
+ );
+ const id = savedObject.id;
+ const actualSpace = await client.get(id);
+
+ expect(actualSpace.solution).toBeUndefined();
+ expect(actualSpace).toEqual(expectedSpace);
+ });
+
+ test(`doesn't strip solution property in traditional build`, async () => {
+ const mockDebugLogger = createMockDebugLogger();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.get.mockResolvedValue({
+ ...savedObject,
+ attributes: { ...(savedObject.attributes as Record), solution: 'search' },
+ });
+ const mockConfig = createMockConfig();
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
+ const id = savedObject.id;
+ const actualSpace = await client.get(id);
+
+ expect(actualSpace).toEqual({ ...expectedSpace, solution: 'search' });
+ });
});
describe('#create', () => {
@@ -219,7 +313,13 @@ describe('#create', () => {
allowFeatureVisibility: true,
});
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
const actualSpace = await client.create(spaceToCreate);
@@ -249,7 +349,13 @@ describe('#create', () => {
allowFeatureVisibility: true,
});
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
await expect(client.create(spaceToCreate)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to create Space, this exceeds the maximum number of spaces set by the xpack.spaces.maxSpaces setting"`
@@ -263,6 +369,93 @@ describe('#create', () => {
expect(mockCallWithRequestRepository.create).not.toHaveBeenCalled();
});
+ test('throws bad request when solution property is provided in serverless build', async () => {
+ const maxSpaces = 5;
+ const mockDebugLogger = createMockDebugLogger();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.create.mockResolvedValue(savedObject);
+ mockCallWithRequestRepository.find.mockResolvedValue({
+ total: maxSpaces - 1,
+ } as any);
+
+ const mockConfig = createMockConfig({
+ enabled: true,
+ maxSpaces,
+ allowFeatureVisibility: true,
+ });
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'serverless'
+ );
+
+ await expect(
+ client.create({ ...spaceToCreate, solution: undefined })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Unable to create Space, solution property is forbidden in serverless"`
+ );
+
+ await expect(
+ client.create({ ...spaceToCreate, solution: 'search' })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Unable to create Space, solution property is forbidden in serverless"`
+ );
+
+ expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({
+ type: 'space',
+ page: 1,
+ perPage: 0,
+ });
+ expect(mockCallWithRequestRepository.create).not.toHaveBeenCalled();
+ });
+
+ test('creates space when solution property is provided in traditional build', async () => {
+ const maxSpaces = 5;
+ const mockDebugLogger = createMockDebugLogger();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.create.mockResolvedValue({
+ ...savedObject,
+ attributes: { ...(savedObject.attributes as Record), solution: 'search' },
+ });
+ mockCallWithRequestRepository.find.mockResolvedValue({
+ total: maxSpaces - 1,
+ } as any);
+
+ const mockConfig = createMockConfig({
+ enabled: true,
+ maxSpaces,
+ allowFeatureVisibility: true,
+ });
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
+
+ const actualSpace = await client.create({ ...spaceToCreate, solution: 'search' });
+
+ expect(actualSpace).toEqual({ ...expectedReturnedSpace, solution: 'search' });
+
+ expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({
+ type: 'space',
+ page: 1,
+ perPage: 0,
+ });
+ expect(mockCallWithRequestRepository.create).toHaveBeenCalledWith(
+ 'space',
+ { ...attributes, solution: 'search' },
+ {
+ id,
+ }
+ );
+ });
+
describe('when config.allowFeatureVisibility is disabled', () => {
test(`creates space without disabledFeatures`, async () => {
const maxSpaces = 5;
@@ -283,7 +476,8 @@ describe('#create', () => {
mockDebugLogger,
mockConfig,
mockCallWithRequestRepository,
- []
+ [],
+ 'traditional'
);
const actualSpace = await client.create(spaceToCreate);
@@ -318,7 +512,8 @@ describe('#create', () => {
mockDebugLogger,
mockConfig,
mockCallWithRequestRepository,
- []
+ [],
+ 'traditional'
);
await expect(
@@ -377,7 +572,13 @@ describe('#update', () => {
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
mockCallWithRequestRepository.get.mockResolvedValue(savedObject);
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
const id = savedObject.id;
const actualSpace = await client.update(id, spaceToUpdate);
@@ -386,6 +587,87 @@ describe('#update', () => {
expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id);
});
+ test('throws bad request when solution property is provided in serverless build', async () => {
+ const mockDebugLogger = createMockDebugLogger();
+ const mockConfig = createMockConfig();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.get.mockResolvedValue(savedObject);
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'serverless'
+ );
+ const id = savedObject.id;
+
+ await expect(
+ client.update(id, { ...spaceToUpdate, solution: undefined })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Unable to update Space, solution property is forbidden in serverless"`
+ );
+
+ await expect(
+ client.update(id, { ...spaceToUpdate, solution: 'search' })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Unable to update Space, solution property is forbidden in serverless"`
+ );
+
+ expect(mockCallWithRequestRepository.update).not.toHaveBeenCalled();
+
+ expect(mockCallWithRequestRepository.get).not.toHaveBeenCalled();
+ });
+
+ test('throws bad request when solution property is undefined in traditional build', async () => {
+ const mockDebugLogger = createMockDebugLogger();
+ const mockConfig = createMockConfig();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.get.mockResolvedValue(savedObject);
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
+ const id = savedObject.id;
+
+ await expect(
+ client.update(id, { ...spaceToUpdate, solution: undefined })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Unable to update Space, solution property cannot be empty"`
+ );
+
+ expect(mockCallWithRequestRepository.update).not.toHaveBeenCalled();
+
+ expect(mockCallWithRequestRepository.get).not.toHaveBeenCalled();
+ });
+
+ test('updates space with solution property using callWithRequestRepository in traditional build', async () => {
+ const mockDebugLogger = createMockDebugLogger();
+ const mockConfig = createMockConfig();
+ const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
+ mockCallWithRequestRepository.get.mockResolvedValue(savedObject);
+
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
+ const id = savedObject.id;
+ await client.update(id, { ...spaceToUpdate, solution: 'search' });
+
+ expect(mockCallWithRequestRepository.update).toHaveBeenCalledWith('space', id, {
+ ...attributes,
+ solution: 'search',
+ });
+ expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id);
+ });
+
describe('when config.allowFeatureVisibility is disabled', () => {
test(`updates space without disabledFeatures`, async () => {
const mockDebugLogger = createMockDebugLogger();
@@ -401,7 +683,8 @@ describe('#update', () => {
mockDebugLogger,
mockConfig,
mockCallWithRequestRepository,
- []
+ [],
+ 'traditional'
);
const id = savedObject.id;
const actualSpace = await client.update(id, spaceToUpdate);
@@ -425,7 +708,8 @@ describe('#update', () => {
mockDebugLogger,
mockConfig,
mockCallWithRequestRepository,
- []
+ [],
+ 'traditional'
);
const id = savedObject.id;
@@ -473,7 +757,13 @@ describe('#delete', () => {
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
mockCallWithRequestRepository.get.mockResolvedValue(reservedSavedObject);
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
await expect(client.delete(id)).rejects.toThrowErrorMatchingInlineSnapshot(
`"The foo space cannot be deleted because it is reserved."`
@@ -488,7 +778,13 @@ describe('#delete', () => {
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
mockCallWithRequestRepository.get.mockResolvedValue(notReservedSavedObject);
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
await client.delete(id);
@@ -504,7 +800,13 @@ describe('#disableLegacyUrlAliases', () => {
const mockConfig = createMockConfig();
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
- const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []);
+ const client = new SpacesClient(
+ mockDebugLogger,
+ mockConfig,
+ mockCallWithRequestRepository,
+ [],
+ 'traditional'
+ );
const aliases = [
{ targetSpace: 'space1', targetType: 'foo', sourceId: '123' },
{ targetSpace: 'space2', targetType: 'bar', sourceId: '456' },
diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts
index cc4058ad22485..536765e8dac47 100644
--- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts
+++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts
@@ -7,6 +7,7 @@
import Boom from '@hapi/boom';
+import type { BuildFlavor } from '@kbn/config/src/types';
import type {
ISavedObjectsPointInTimeFinder,
ISavedObjectsRepository,
@@ -80,12 +81,17 @@ export interface ISpacesClient {
* Client for interacting with spaces.
*/
export class SpacesClient implements ISpacesClient {
+ private isServerless = false;
+
constructor(
private readonly debugLogger: (message: string) => void,
private readonly config: ConfigType,
private readonly repository: ISavedObjectsRepository,
- private readonly nonGlobalTypeNames: string[]
- ) {}
+ private readonly nonGlobalTypeNames: string[],
+ private readonly buildFlavour: BuildFlavor
+ ) {
+ this.isServerless = this.buildFlavour === 'serverless';
+ }
public async getAll(options: v1.GetAllSpacesOptions = {}): Promise {
const { purpose = DEFAULT_PURPOSE } = options;
@@ -130,10 +136,19 @@ export class SpacesClient implements ISpacesClient {
);
}
+ if (this.isServerless && space.hasOwnProperty('solution')) {
+ throw Boom.badRequest('Unable to create Space, solution property is forbidden in serverless');
+ }
+
+ if (space.hasOwnProperty('solution') && !space.solution) {
+ throw Boom.badRequest('Unable to create Space, solution property cannot be empty');
+ }
+
this.debugLogger(`SpacesClient.create(), using RBAC. Attempting to create space`);
const id = space.id;
const attributes = this.generateSpaceAttributes(space);
+
const createdSavedObject = await this.repository.create('space', attributes, { id });
this.debugLogger(`SpacesClient.create(), created space object`);
@@ -148,6 +163,14 @@ export class SpacesClient implements ISpacesClient {
);
}
+ if (this.isServerless && space.hasOwnProperty('solution')) {
+ throw Boom.badRequest('Unable to update Space, solution property is forbidden in serverless');
+ }
+
+ if (space.hasOwnProperty('solution') && !space.solution) {
+ throw Boom.badRequest('Unable to update Space, solution property cannot be empty');
+ }
+
const attributes = this.generateSpaceAttributes(space);
await this.repository.update('space', id, attributes);
const updatedSavedObject = await this.repository.get('space', id);
@@ -181,7 +204,7 @@ export class SpacesClient implements ISpacesClient {
await this.repository.bulkUpdate(objectsToUpdate);
}
- private transformSavedObjectToSpace(savedObject: SavedObject): v1.Space {
+ private transformSavedObjectToSpace = (savedObject: SavedObject): v1.Space => {
return {
id: savedObject.id,
name: savedObject.attributes.name ?? '',
@@ -191,10 +214,11 @@ export class SpacesClient implements ISpacesClient {
imageUrl: savedObject.attributes.imageUrl,
disabledFeatures: savedObject.attributes.disabledFeatures ?? [],
_reserved: savedObject.attributes._reserved,
+ ...(!this.isServerless ? { solution: savedObject.attributes.solution } : {}),
} as v1.Space;
- }
+ };
- private generateSpaceAttributes(space: v1.Space) {
+ private generateSpaceAttributes = (space: v1.Space) => {
return {
name: space.name,
description: space.description,
@@ -202,6 +226,7 @@ export class SpacesClient implements ISpacesClient {
initials: space.initials,
imageUrl: space.imageUrl,
disabledFeatures: space.disabledFeatures,
+ ...(!this.isServerless && space.solution ? { solution: space.solution } : {}),
};
- }
+ };
}
diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts
index 455387a816bd5..cedcdec858e55 100644
--- a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts
+++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts
@@ -20,7 +20,7 @@ const debugLogger = jest.fn();
describe('SpacesClientService', () => {
describe('#setup', () => {
it('allows a single repository factory to be set', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
const setup = service.setup({ config$: Rx.of(spacesConfig) });
const repositoryFactory = jest.fn();
@@ -32,7 +32,7 @@ describe('SpacesClientService', () => {
});
it('allows a single client wrapper to be set', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
const setup = service.setup({ config$: Rx.of(spacesConfig) });
const clientWrapper = jest.fn();
@@ -46,7 +46,7 @@ describe('SpacesClientService', () => {
describe('#start', () => {
it('throws if config is not available', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
service.setup({ config$: new Rx.Observable() });
const coreStart = coreMock.createStart();
const start = service.start(coreStart);
@@ -60,7 +60,7 @@ describe('SpacesClientService', () => {
describe('without a custom repository factory or wrapper', () => {
it('returns an instance of the spaces client using the scoped repository', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
service.setup({ config$: Rx.of(spacesConfig) });
const coreStart = coreMock.createStart();
@@ -78,7 +78,7 @@ describe('SpacesClientService', () => {
});
it('uses the custom repository factory when set', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
const setup = service.setup({ config$: Rx.of(spacesConfig) });
const customRepositoryFactory = jest.fn();
@@ -98,7 +98,7 @@ describe('SpacesClientService', () => {
});
it('wraps the client in the wrapper when registered', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
const setup = service.setup({ config$: Rx.of(spacesConfig) });
const wrapper = Symbol() as unknown as ISpacesClient;
@@ -123,7 +123,7 @@ describe('SpacesClientService', () => {
});
it('wraps the client in the wrapper when registered, using the custom repository factory when configured', () => {
- const service = new SpacesClientService(debugLogger);
+ const service = new SpacesClientService(debugLogger, 'traditional');
const setup = service.setup({ config$: Rx.of(spacesConfig) });
const customRepositoryFactory = jest.fn();
diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts
index 8a6c0eaab0d3b..26d4fef85ea6d 100644
--- a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts
+++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts
@@ -7,6 +7,7 @@
import type { Observable } from 'rxjs';
+import type { BuildFlavor } from '@kbn/config/src/types';
import type {
CoreStart,
ISavedObjectsRepository,
@@ -72,7 +73,10 @@ export class SpacesClientService {
private clientWrapper?: SpacesClientWrapper;
- constructor(private readonly debugLogger: (message: string) => void) {}
+ constructor(
+ private readonly debugLogger: (message: string) => void,
+ private readonly buildFlavour: BuildFlavor
+ ) {}
public setup({ config$ }: SetupDeps): SpacesClientServiceSetup {
config$.subscribe((nextConfig) => {
@@ -117,7 +121,8 @@ export class SpacesClientService {
this.debugLogger,
this.config,
this.repositoryFactory!(request, coreStart.savedObjects),
- nonGlobalTypeNames
+ nonGlobalTypeNames,
+ this.buildFlavour
);
if (this.clientWrapper) {
return this.clientWrapper(request, baseClient);
diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts
index 694fb5b69e46a..8222b43018b2e 100644
--- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts
+++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts
@@ -69,7 +69,7 @@ const createService = (serverBasePath: string = '') => {
basePath: httpSetup.basePath,
});
- const spacesClientService = new SpacesClientService(jest.fn());
+ const spacesClientService = new SpacesClientService(jest.fn(), 'traditional');
spacesClientService.setup({
config$: Rx.of(spacesConfig),
});
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index a194bc656b28b..f73cbc46de222 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -5062,27 +5062,15 @@
"kbn-esql-validation-autocomplete.esql.definition.orDoc": "ou",
"kbn-esql-validation-autocomplete.esql.definition.rlikeDoc": "Filtrer les données en fonction des expressions régulières des chaînes",
"kbn-esql-validation-autocomplete.esql.definition.subtractDoc": "Subtract (-)",
- "kbn-esql-validation-autocomplete.esql.definitions.absDoc": "Renvoie la valeur absolue.",
- "kbn-esql-validation-autocomplete.esql.definitions.acosDoc": "Fonction trigonométrique cosinus inverse",
"kbn-esql-validation-autocomplete.esql.definitions.appendSeparatorDoc": "Le ou les caractères qui séparent les champs ajoutés. A pour valeur par défaut une chaîne vide (\"\").",
"kbn-esql-validation-autocomplete.esql.definitions.asDoc": "En tant que",
- "kbn-esql-validation-autocomplete.esql.definitions.asinDoc": "Fonction trigonométrique sinus inverse",
- "kbn-esql-validation-autocomplete.esql.definitions.atan2Doc": "L'angle entre l'axe positif des x et le rayon allant de l'origine au point (x , y) dans le plan cartésien",
- "kbn-esql-validation-autocomplete.esql.definitions.atanDoc": "Fonction trigonométrique tangente inverse",
"kbn-esql-validation-autocomplete.esql.definitions.autoBucketDoc": "Groupement automatique des dates en fonction d'une plage et d'un compartiment cible donnés.",
"kbn-esql-validation-autocomplete.esql.definitions.avgDoc": "Renvoie la moyenne des valeurs dans un champ",
"kbn-esql-validation-autocomplete.esql.definitions.byDoc": "Par",
- "kbn-esql-validation-autocomplete.esql.definitions.caseDoc": "Accepte les paires de conditions et de valeurs. La fonction renvoie la valeur correspondant à la première condition évaluée à `true` (vraie). Si le nombre d'arguments est impair, le dernier argument est la valeur par défaut qui est renvoyée si aucune condition ne correspond.",
"kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc": "L'enrichissement a lieu sur n'importe quel cluster",
"kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc": "L'enrichissement a lieu sur le cluster de coordination qui reçoit une requête ES|QL",
"kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc": "Mode de requête inter-clusters",
"kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc": "L'enrichissement a lieu sur le cluster qui héberge l'index cible.",
- "kbn-esql-validation-autocomplete.esql.definitions.ceilDoc": "Arrondir un nombre à l'entier supérieur.",
- "kbn-esql-validation-autocomplete.esql.definitions.cidrMatchDoc": "La fonction utilise un premier paramètre de type adresse IP, puis un ou plusieurs paramètres évalués en fonction d'une spécification CIDR.",
- "kbn-esql-validation-autocomplete.esql.definitions.coalesceDoc": "Renvoie la première valeur non nulle.",
- "kbn-esql-validation-autocomplete.esql.definitions.concatDoc": "Concatène deux ou plusieurs chaînes.",
- "kbn-esql-validation-autocomplete.esql.definitions.cosDoc": "Fonction trigonométrique cosinus",
- "kbn-esql-validation-autocomplete.esql.definitions.coshDoc": "Fonction hyperbolique cosinus",
"kbn-esql-validation-autocomplete.esql.definitions.countDistinctDoc": "Renvoie le décompte des valeurs distinctes dans un champ.",
"kbn-esql-validation-autocomplete.esql.definitions.countDoc": "Renvoie le décompte des valeurs dans un champ.",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.day": "Jour",
@@ -5101,87 +5089,29 @@
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.weeks": "Semaines (pluriel)",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.year": "An",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.years": "Ans (pluriel)",
- "kbn-esql-validation-autocomplete.esql.definitions.dateExtractDoc": "Extrait des parties d'une date, telles que l'année, le mois, le jour, l'heure. Les types de champs pris en charge sont ceux fournis par la fonction \"java.time.temporal.ChronoField\"",
- "kbn-esql-validation-autocomplete.esql.definitions.dateFormatDoc": "Renvoie une représentation sous forme de chaîne d'une date dans le format fourni. Si aucun format n'est indiqué, le format \"yyyy-MM-dd'T'HH:mm:ss.SSSZ\" est utilisé.",
- "kbn-esql-validation-autocomplete.esql.definitions.dateParseDoc": "Analyser les dates à partir de chaînes.",
- "kbn-esql-validation-autocomplete.esql.definitions.dateTruncDoc": "Arrondit une date à l'intervalle le plus proche. Les intervalles peuvent être exprimés à l'aide de la syntaxe littérale timespan.",
"kbn-esql-validation-autocomplete.esql.definitions.dissectDoc": "Extrait de multiples valeurs de chaîne à partir d'une entrée de chaîne unique, suivant un modèle",
"kbn-esql-validation-autocomplete.esql.definitions.dropDoc": "Supprime les colonnes",
- "kbn-esql-validation-autocomplete.esql.definitions.eDoc": "Nombre d'Euler.",
- "kbn-esql-validation-autocomplete.esql.definitions.endsWithDoc": "Renvoie une valeur booléenne qui indique si une chaîne de mots-clés se termine par une autre chaîne :",
"kbn-esql-validation-autocomplete.esql.definitions.enrichDoc": "Enrichissez le tableau à l'aide d'un autre tableau. Avant de pouvoir utiliser l'enrichissement, vous devez créer et exécuter une politique d'enrichissement.",
"kbn-esql-validation-autocomplete.esql.definitions.evalDoc": "Calcule une expression et place la valeur résultante dans un champ de résultats de recherche.",
- "kbn-esql-validation-autocomplete.esql.definitions.floorDoc": "Arrondir un nombre à l'entier inférieur.",
"kbn-esql-validation-autocomplete.esql.definitions.fromDoc": "Récupère les données à partir d'un ou plusieurs flux de données, index ou alias. Dans une requête ou une sous-requête, vous devez utiliser d'abord la commande from, et cette dernière ne nécessite pas de barre verticale au début. Par exemple, pour récupérer des données d'un index :",
- "kbn-esql-validation-autocomplete.esql.definitions.greatestDoc": "Renvoie la valeur maximale de plusieurs colonnes.",
"kbn-esql-validation-autocomplete.esql.definitions.grokDoc": "Extrait de multiples valeurs de chaîne à partir d'une entrée de chaîne unique, suivant un modèle",
"kbn-esql-validation-autocomplete.esql.definitions.keepDoc": "Réarrange les champs dans le tableau d'entrée en appliquant les clauses \"KEEP\" dans les champs",
- "kbn-esql-validation-autocomplete.esql.definitions.leastDoc": "Renvoie la valeur minimale de plusieurs colonnes.",
- "kbn-esql-validation-autocomplete.esql.definitions.leftDoc": "Renvoyer la sous-chaîne qui extrait la longueur des caractères de la chaîne en partant de la gauche.",
- "kbn-esql-validation-autocomplete.esql.definitions.lengthDoc": "Renvoie la longueur des caractères d'une chaîne.",
"kbn-esql-validation-autocomplete.esql.definitions.limitDoc": "Renvoie les premiers résultats de recherche, dans l'ordre de recherche, en fonction de la \"limite\" spécifiée.",
- "kbn-esql-validation-autocomplete.esql.definitions.log10Doc": "Renvoie le log de base 10.",
- "kbn-esql-validation-autocomplete.esql.definitions.logDoc": "La fonction scalaire log(based, value) renvoie le logarithme d'une valeur pour une base spécifique, comme défini dans l'argument",
- "kbn-esql-validation-autocomplete.esql.definitions.ltrimDoc": "Retire les espaces au début des chaînes.",
"kbn-esql-validation-autocomplete.esql.definitions.maxDoc": "Renvoie la valeur maximale dans un champ.",
"kbn-esql-validation-autocomplete.esql.definitions.medianDeviationDoc": "Renvoie la médiane de chaque écart de point de données par rapport à la médiane de l'ensemble de l'échantillon.",
"kbn-esql-validation-autocomplete.esql.definitions.medianDoc": "Renvoie le 50e centile.",
"kbn-esql-validation-autocomplete.esql.definitions.metadataDoc": "Métadonnées",
"kbn-esql-validation-autocomplete.esql.definitions.minDoc": "Renvoie la valeur minimale dans un champ.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvAvgDoc": "Convertit un champ multivalué en un champ à valeur unique comprenant la moyenne de toutes les valeurs.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvConcatDoc": "Convertit un champ de type chaîne multivalué en un champ à valeur unique comprenant la concaténation de toutes les valeurs, séparées par un délimiteur",
- "kbn-esql-validation-autocomplete.esql.definitions.mvCountDoc": "Convertit un champ multivalué en un champ à valeur unique comprenant le total du nombre de valeurs",
- "kbn-esql-validation-autocomplete.esql.definitions.mvDedupeDoc": "Supprime les doublons d'un champ multivalué",
"kbn-esql-validation-autocomplete.esql.definitions.mvExpandDoc": "Développe des champs comportant des valeurs multiples en indiquant une valeur par ligne et en dupliquant les autres champs",
- "kbn-esql-validation-autocomplete.esql.definitions.mvFirstDoc": "Réduit un champ multivalué en un champ à valeur unique comprenant la première valeur.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvLastDoc": "Réduit un champ multivalué en un champ à valeur unique comprenant la dernière valeur.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMaxDoc": "Convertit un champ multivalué en un champ à valeur unique comprenant la valeur maximale.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMedianDoc": "Convertit un champ multivalué en un champ à valeur unique comprenant la valeur médiane.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMinDoc": "Convertit un champ multivalué en un champ à valeur unique comprenant la valeur minimale.",
- "kbn-esql-validation-autocomplete.esql.definitions.mvSumDoc": "Convertit un champ multivalué en un champ à valeur unique comprenant la somme de toutes les valeurs.",
- "kbn-esql-validation-autocomplete.esql.definitions.nowDoc": "Renvoie la date et l'heure actuelles.",
"kbn-esql-validation-autocomplete.esql.definitions.onDoc": "Activé",
"kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc": "Renvoie le n-ième centile d'un champ.",
- "kbn-esql-validation-autocomplete.esql.definitions.piDoc": "Le rapport entre la circonférence et le diamètre d'un cercle.",
- "kbn-esql-validation-autocomplete.esql.definitions.powDoc": "Renvoie la valeur d'une base (premier argument) élevée à une puissance (deuxième argument).",
"kbn-esql-validation-autocomplete.esql.definitions.renameDoc": "Attribue un nouveau nom à une ancienne colonne",
- "kbn-esql-validation-autocomplete.esql.definitions.replaceDoc": "La fonction remplace dans la chaîne (1er argument) toutes les correspondances avec l'expression régulière (2e argument) par la chaîne de remplacement (3e argument). Si l'un des arguments est NULL, le résultat est NULL.",
- "kbn-esql-validation-autocomplete.esql.definitions.rightDoc": "Renvoyer la sous-chaîne qui extrait la longueur des caractères de la chaîne en partant de la droite.",
- "kbn-esql-validation-autocomplete.esql.definitions.roundDoc": "Renvoie un nombre arrondi à la décimale, spécifié par la valeur entière la plus proche. La valeur par défaut est arrondie à un entier.",
"kbn-esql-validation-autocomplete.esql.definitions.rowDoc": "Renvoie une ligne contenant une ou plusieurs colonnes avec les valeurs que vous spécifiez. Cette commande peut s'avérer utile pour les tests.",
- "kbn-esql-validation-autocomplete.esql.definitions.rtrimDoc": "Supprime les espaces à la fin des chaînes.",
"kbn-esql-validation-autocomplete.esql.definitions.showDoc": "Renvoie des informations sur le déploiement et ses capacités",
- "kbn-esql-validation-autocomplete.esql.definitions.sinDoc": "Fonction trigonométrique sinus.",
- "kbn-esql-validation-autocomplete.esql.definitions.sinhDoc": "Fonction hyperbolique sinus.",
"kbn-esql-validation-autocomplete.esql.definitions.sortDoc": "Trie tous les résultats en fonction des champs spécifiés. Par défaut, les valeurs null sont considérées comme supérieures à toutes les autres valeurs. Avec l'ordre de tri croissant, les valeurs null sont classées en dernier. Avec l'ordre de tri décroissant, elles sont classées en premier. Pour modifier cet ordre, utilisez NULLS FIRST ou NULLS LAST",
- "kbn-esql-validation-autocomplete.esql.definitions.splitDoc": "Divise une chaîne de valeur unique en plusieurs chaînes.",
- "kbn-esql-validation-autocomplete.esql.definitions.sqrtDoc": "Renvoie la racine carrée d'un nombre. ",
- "kbn-esql-validation-autocomplete.esql.definitions.startsWithDoc": "Renvoie un booléen qui indique si une chaîne de mot-clés débute par une autre chaîne.",
"kbn-esql-validation-autocomplete.esql.definitions.statsDoc": "Calcule les statistiques agrégées, telles que la moyenne, le décompte et la somme, sur l'ensemble des résultats de recherche entrants. Comme pour l'agrégation SQL, si la commande stats est utilisée sans clause BY, une seule ligne est renvoyée, qui est l'agrégation de tout l'ensemble des résultats de recherche entrants. Lorsque vous utilisez une clause BY, une ligne est renvoyée pour chaque valeur distincte dans le champ spécifié dans la clause BY. La commande stats renvoie uniquement les champs dans l'agrégation, et vous pouvez utiliser un large éventail de fonctions statistiques avec la commande stats. Lorsque vous effectuez plusieurs agrégations, séparez chacune d'entre elle par une virgule.",
"kbn-esql-validation-autocomplete.esql.definitions.stCentroidDoc": "Renvoie le décompte des valeurs distinctes dans un champ.",
- "kbn-esql-validation-autocomplete.esql.definitions.substringDoc": "Renvoie la sous-chaîne d'une chaîne, délimitée en fonction d'une position de départ et d'une longueur facultative.",
"kbn-esql-validation-autocomplete.esql.definitions.sumDoc": "Renvoie la somme des valeurs dans un champ.",
- "kbn-esql-validation-autocomplete.esql.definitions.tanDoc": "Fonction trigonométrique tangente.",
- "kbn-esql-validation-autocomplete.esql.definitions.tanhDoc": "Fonction hyperbolique tangente.",
- "kbn-esql-validation-autocomplete.esql.definitions.tauDoc": "Le rapport entre la circonférence et le rayon d'un cercle.",
- "kbn-esql-validation-autocomplete.esql.definitions.toBooleanDoc": "Convertit en booléen.",
- "kbn-esql-validation-autocomplete.esql.definitions.toCartesianPointDoc": "Convertit la valeur d'une entrée en une valeur \"point\".",
- "kbn-esql-validation-autocomplete.esql.definitions.toCartesianshapeDoc": "Convertit la valeur d'une entrée en une valeur cartesian_shape.",
- "kbn-esql-validation-autocomplete.esql.definitions.toDateTimeDoc": "Convertit en date.",
- "kbn-esql-validation-autocomplete.esql.definitions.toDegreesDoc": "Convertit en degrés",
- "kbn-esql-validation-autocomplete.esql.definitions.toDoubleDoc": "Convertit en double.",
- "kbn-esql-validation-autocomplete.esql.definitions.toGeopointDoc": "Convertit en une valeur geo_point.",
- "kbn-esql-validation-autocomplete.esql.definitions.toGeoshapeDoc": "Convertit la valeur d'une entrée en une valeur geo_shape.",
- "kbn-esql-validation-autocomplete.esql.definitions.toIntegerDoc": "Convertit en nombre entier.",
- "kbn-esql-validation-autocomplete.esql.definitions.toIpDoc": "Convertit en ip.",
- "kbn-esql-validation-autocomplete.esql.definitions.toLongDoc": "Convertit en long.",
- "kbn-esql-validation-autocomplete.esql.definitions.toLowerDoc": "Renvoie une nouvelle chaîne représentant la chaîne d'entrée convertie en minuscules.",
- "kbn-esql-validation-autocomplete.esql.definitions.toRadiansDoc": "Convertit en radians",
- "kbn-esql-validation-autocomplete.esql.definitions.toStringDoc": "Convertit en chaîne.",
- "kbn-esql-validation-autocomplete.esql.definitions.toUnsignedLongDoc": "Convertit en long non signé.",
- "kbn-esql-validation-autocomplete.esql.definitions.toUpperDoc": "Renvoie une nouvelle chaîne représentant la chaîne d'entrée convertie en majuscules.",
- "kbn-esql-validation-autocomplete.esql.definitions.toVersionDoc": "Convertit en version.",
- "kbn-esql-validation-autocomplete.esql.definitions.trimDoc": "Supprime les espaces de début et de fin d'une chaîne.",
"kbn-esql-validation-autocomplete.esql.definitions.whereDoc": "Utilise \"predicate-expressions\" pour filtrer les résultats de recherche. Une expression predicate, lorsqu'elle est évaluée, renvoie TRUE ou FALSE. La commande where renvoie uniquement les résultats qui donnent la valeur TRUE. Par exemple, pour filtrer les résultats pour une valeur de champ spécifique",
"kbn-esql-validation-autocomplete.esql.definitions.withDoc": "Avec",
"kbn-esql-validation-autocomplete.esql.quickfix.replaceWithQuote": "Remplacer les guillemets par le signe \" (double)",
@@ -25534,7 +25464,6 @@
"xpack.ml.actions.clearSelectionTitle": "Effacer la sélection",
"xpack.ml.actions.createADJobFromLens": "Créer une tâche de détection des anomalies",
"xpack.ml.actions.createADJobFromPatternAnalysis": "Créer une tâche de catégorisation et de détection des anomalies",
- "xpack.ml.actions.editAnomalyChartsTitle": "Modifier les graphiques d'anomalies",
"xpack.ml.actions.openInAnomalyExplorerTitle": "Ouvrir dans Anomaly Explorer",
"xpack.ml.actions.runPatternAnalysis.description": "Déclenché lorsque l'utilisateur souhaite effectuer une analyse du modèle sur un champ.",
"xpack.ml.actions.runPatternAnalysis.title": "Exécuter l'analyse du modèle",
@@ -25743,7 +25672,6 @@
"xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel": "Nombre maximal de séries à tracer",
"xpack.ml.anomalyChartsEmbeddable.panelTitleLabel": "Titre du panneau",
"xpack.ml.anomalyChartsEmbeddable.setupModal.cancelButtonLabel": "Annuler",
- "xpack.ml.anomalyChartsEmbeddable.setupModal.confirmButtonLabel": "Confirmer les configurations",
"xpack.ml.anomalyChartsEmbeddable.setupModal.title": "Configuration des graphiques Anomaly Explorer",
"xpack.ml.anomalyDetection.anomalyExplorer.docTitle": "Anomaly Explorer (Explorateur d'anomalies)",
"xpack.ml.anomalyDetection.anomalyExplorerLabel": "Anomaly Explorer (Explorateur d'anomalies)",
@@ -40986,7 +40914,6 @@
"xpack.synthetics.monitorList.viewInDiscover": "Afficher dans Discover",
"xpack.synthetics.monitorLocation.locationContextMenuTitleLabel": "Accéder à l'emplacement",
"xpack.synthetics.monitorLocation.locationLabel": "Emplacement",
- "xpack.synthetics.monitorManagement.accessRestricted": "Votre accès à l'infrastructure de test gérée à l'échelle mondiale est limité.",
"xpack.synthetics.monitorManagement.actions": "Actions",
"xpack.synthetics.monitorManagement.addAgentPolicyDesc": "Les emplacements privés nécessitent une politique d'agent. Pour ajouter un emplacement privé, vous devez d'abord créer une politique d'agent dans Fleet.",
"xpack.synthetics.monitorManagement.addEdit.createMonitorLabel": "Créer le moniteur",
@@ -41045,10 +40972,8 @@
"xpack.synthetics.monitorManagement.getProjectApiKey.label": "Générer une clé d'API de projet",
"xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "Générer une clé d'API de projet",
"xpack.synthetics.monitorManagement.inProgress": "EN COURS",
- "xpack.synthetics.monitorManagement.label": "Application Synthetics",
"xpack.synthetics.monitorManagement.learnMore": "Pour en savoir plus,",
"xpack.synthetics.monitorManagement.learnMore.label": "En savoir plus",
- "xpack.synthetics.monitorManagement.loading.label": "Charger une application Synthetics",
"xpack.synthetics.monitorManagement.loadingSteps": "Chargement des étapes...",
"xpack.synthetics.monitorManagement.locationName": "Nom de l’emplacement",
"xpack.synthetics.monitorManagement.locationsLabel": "Emplacements",
@@ -41086,7 +41011,6 @@
"xpack.synthetics.monitorManagement.projectDelete.docsLink": "En savoir plus",
"xpack.synthetics.monitorManagement.projectPush.label": "Commande push du projet",
"xpack.synthetics.monitorManagement.readDocs": "lire les documents",
- "xpack.synthetics.monitorManagement.requestAccess": "Demander un accès",
"xpack.synthetics.monitorManagement.saveLabel": "Enregistrer",
"xpack.synthetics.monitorManagement.selectOneOrMoreLocations": "Sélectionnez un ou plusieurs emplacements.",
"xpack.synthetics.monitorManagement.selectOneOrMoreLocationsDetails": "Sélectionnez les emplacements où les moniteurs seront exécutés.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 252712bbd8b0e..42ef3c2abf137 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5054,27 +5054,15 @@
"kbn-esql-validation-autocomplete.esql.definition.orDoc": "または",
"kbn-esql-validation-autocomplete.esql.definition.rlikeDoc": "文字列の正規表現に基づいてデータをフィルター",
"kbn-esql-validation-autocomplete.esql.definition.subtractDoc": "減算(-)",
- "kbn-esql-validation-autocomplete.esql.definitions.absDoc": "絶対値を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.acosDoc": "逆余弦三角関数",
"kbn-esql-validation-autocomplete.esql.definitions.appendSeparatorDoc": "追加されたフィールドを区切る文字。デフォルトは空の文字列(\"\")です。",
"kbn-esql-validation-autocomplete.esql.definitions.asDoc": "として",
- "kbn-esql-validation-autocomplete.esql.definitions.asinDoc": "逆正弦三角関数",
- "kbn-esql-validation-autocomplete.esql.definitions.atan2Doc": "直交平面上の原点から点(x , y)に向かう光線と正のx軸のなす角",
- "kbn-esql-validation-autocomplete.esql.definitions.atanDoc": "逆正接三角関数",
"kbn-esql-validation-autocomplete.esql.definitions.autoBucketDoc": "指定された範囲とバケット目標に基づいて、日付を自動的にバケット化します。",
"kbn-esql-validation-autocomplete.esql.definitions.avgDoc": "フィールドの値の平均を返します",
"kbn-esql-validation-autocomplete.esql.definitions.byDoc": "グループ基準",
- "kbn-esql-validation-autocomplete.esql.definitions.caseDoc": "条件と値のペアを指定できます。この関数は、最初にtrueと評価された条件に属する値を返します。引数の数が奇数の場合、最後の引数は条件に一致しない場合に返されるデフォルト値になります。",
"kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc": "エンリッチは任意のクラスターで発生します",
"kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc": "エンリッチは、ES|QLを受信する調整クラスターで実行されます",
"kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc": "クラスター横断クエリモード",
"kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc": "エンリッチはターゲットインデックスをホスティングするクラスターで発生します。",
- "kbn-esql-validation-autocomplete.esql.definitions.ceilDoc": "最も近い整数に数値を切り上げます。",
- "kbn-esql-validation-autocomplete.esql.definitions.cidrMatchDoc": "この関数は、IP型の最初のパラメーターを取り、その後にCIDR指定に対して評価された1つ以上のパラメーターを取ります。",
- "kbn-esql-validation-autocomplete.esql.definitions.coalesceDoc": "最初のNULL以外の値を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.concatDoc": "2つ以上の文字列を連結します。",
- "kbn-esql-validation-autocomplete.esql.definitions.cosDoc": "余弦三角関数",
- "kbn-esql-validation-autocomplete.esql.definitions.coshDoc": "余弦双曲線関数",
"kbn-esql-validation-autocomplete.esql.definitions.countDistinctDoc": "フィールド内の異なる値の数を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.countDoc": "フィールドの値の数を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.day": "日",
@@ -5093,87 +5081,29 @@
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.weeks": "週(複数)",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.year": "年",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.years": "年(複数)",
- "kbn-esql-validation-autocomplete.esql.definitions.dateExtractDoc": "年、月、日、時間など、日付の一部を抽出します。サポートされているフィールド型はjava.time.temporal.ChronoFieldで提供されている型です。",
- "kbn-esql-validation-autocomplete.esql.definitions.dateFormatDoc": "指定した書式の日付の文字列表現を返します。書式が指定されていない場合は、yyyy-MM-dd'T'HH:mm:ss.SSSZの書式が使用されます。",
- "kbn-esql-validation-autocomplete.esql.definitions.dateParseDoc": "文字列から日付を解析します。",
- "kbn-esql-validation-autocomplete.esql.definitions.dateTruncDoc": "最も近い区間まで日付を切り捨てます。区間はtimespanリテラル構文を使って表現できます。",
"kbn-esql-validation-autocomplete.esql.definitions.dissectDoc": "単一の文字列入力から、パターンに基づいて複数の文字列値を抽出",
"kbn-esql-validation-autocomplete.esql.definitions.dropDoc": "列を削除",
- "kbn-esql-validation-autocomplete.esql.definitions.eDoc": "Eulerの数値。",
- "kbn-esql-validation-autocomplete.esql.definitions.endsWithDoc": "キーワード文字列が他の文字列で終わるかどうかを示すブール値を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.enrichDoc": "別のテーブルでテーブルをエンリッチします。エンリッチを使用する前に、エンリッチポリシーを作成して実行する必要があります。",
"kbn-esql-validation-autocomplete.esql.definitions.evalDoc": "式を計算し、結果の値を検索結果フィールドに入力します。",
- "kbn-esql-validation-autocomplete.esql.definitions.floorDoc": "最も近い整数に数値を切り捨てます。",
"kbn-esql-validation-autocomplete.esql.definitions.fromDoc": "1つ以上のデータストリーム、インデックス、またはエイリアスからデータを取得します。クエリまたはサブクエリでは、最初にコマンドから使用する必要があります。先頭のパイプは不要です。たとえば、インデックスからデータを取得します。",
- "kbn-esql-validation-autocomplete.esql.definitions.greatestDoc": "多数の列から最大値を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.grokDoc": "単一の文字列入力から、パターンに基づいて複数の文字列値を抽出",
"kbn-esql-validation-autocomplete.esql.definitions.keepDoc": "フィールドでkeep句を適用して、入力テーブルのフィールドを並べ替えます",
- "kbn-esql-validation-autocomplete.esql.definitions.leastDoc": "多数の列から最小値を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.leftDoc": "stringから左から順にlength文字を抜き出したサブ文字列を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.lengthDoc": "文字列の文字数を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.limitDoc": "指定された「制限」に基づき、検索順序で、最初の検索結果を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.log10Doc": "底が10の対数を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.logDoc": "スカラー関数対数(底、値)は、引数で指定されている特定の底の値の対数を返します",
- "kbn-esql-validation-autocomplete.esql.definitions.ltrimDoc": "文字列から先頭の空白を取り除きます。",
"kbn-esql-validation-autocomplete.esql.definitions.maxDoc": "フィールドの最大値を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.medianDeviationDoc": "サンプル全体の中央値からの各データポイントの偏差の中央値を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.medianDoc": "50%パーセンタイルを返します。",
"kbn-esql-validation-autocomplete.esql.definitions.metadataDoc": "メタデータ",
"kbn-esql-validation-autocomplete.esql.definitions.minDoc": "フィールドの最小値を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvAvgDoc": "複数値フィールドを、すべての値の平均を含む単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvConcatDoc": "複数値文字列フィールドを、区切り文字で区切られたすべての値を連結した単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvCountDoc": "複数値フィールドを、値の数をカウントする単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvDedupeDoc": "複数値フィールドから重複を削除します。",
"kbn-esql-validation-autocomplete.esql.definitions.mvExpandDoc": "複数値フィールドを値ごとに1行に展開し、他のフィールドを複製します",
- "kbn-esql-validation-autocomplete.esql.definitions.mvFirstDoc": "複数値フィールドを、最初の値を含む単一値フィールドに縮小します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvLastDoc": "複数値フィールドを、最後の値を含む単一値フィールドに縮小します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMaxDoc": "複数値フィールドを、最大値を含む単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMedianDoc": "複数値フィールドを、中央値を含む単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMinDoc": "複数値フィールドを、最小値を含む単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvSumDoc": "複数値フィールドを、すべての値の合計を含む単一値フィールドに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.nowDoc": "現在の日付と時刻を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.onDoc": "オン",
"kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc": "フィールドのnパーセンタイルを返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.piDoc": "円の円周と直径の比率。",
- "kbn-esql-validation-autocomplete.esql.definitions.powDoc": "底(第1引数)を累乗(第2引数)した値を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.renameDoc": "古い列の名前を新しい列に変更",
- "kbn-esql-validation-autocomplete.esql.definitions.replaceDoc": "この関数は、文字列(第1引数)で、正規表現(第2引数)の任意の一致に置換文字列(第3引数)を代入します。いずれかの引数がNULLの場合、結果はNULLになります。",
- "kbn-esql-validation-autocomplete.esql.definitions.rightDoc": "stringのうち右から数えてlength文字までのサブ文字列を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.roundDoc": "最も近い整数値で指定された数字まで端数処理された数値を返します。デフォルトは整数になるように四捨五入されます。",
"kbn-esql-validation-autocomplete.esql.definitions.rowDoc": "指定した値の列を1つ以上含む行を作成します。これはテストの場合に便利です。",
- "kbn-esql-validation-autocomplete.esql.definitions.rtrimDoc": "文字列から末尾の空白を取り除きます。",
"kbn-esql-validation-autocomplete.esql.definitions.showDoc": "デプロイとその能力に関する情報を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.sinDoc": "正弦三角関数。",
- "kbn-esql-validation-autocomplete.esql.definitions.sinhDoc": "正弦双曲線関数。",
"kbn-esql-validation-autocomplete.esql.definitions.sortDoc": "すべての結果を指定されたフィールドで並べ替えます。デフォルトでは、null値は他のどの値よりも大きい値として扱われます。昇順のソートではnull値は最後にソートされ、降順のソートではnull値は最初にソートされます。NULLS FIRSTまたはNULLS LASTを指定することで変更できます。",
- "kbn-esql-validation-autocomplete.esql.definitions.splitDoc": "単一の値の文字列を複数の文字列に分割します。",
- "kbn-esql-validation-autocomplete.esql.definitions.sqrtDoc": "数値の平方根を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.startsWithDoc": "キーワード文字列が他の文字列で始まるかどうかを示すブール値を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.statsDoc": "受信検索結果セットで、平均、カウント、合計などの集約統計情報を計算します。SQL集約と同様に、statsコマンドをBY句なしで使用した場合は、1行のみが返されます。これは、受信検索結果セット全体に対する集約です。BY句を使用すると、BY句で指定したフィールドの1つの値ごとに1行が返されます。statsコマンドは集約のフィールドのみを返します。statsコマンドではさまざまな統計関数を使用できます。複数の集約を実行するときには、各集約をカンマで区切ります。",
"kbn-esql-validation-autocomplete.esql.definitions.stCentroidDoc": "フィールド内の異なる値の数を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.substringDoc": "文字列のサブ文字列を、開始位置とオプションの長さで指定して返します。",
"kbn-esql-validation-autocomplete.esql.definitions.sumDoc": "フィールドの値の合計を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.tanDoc": "正接三角関数。",
- "kbn-esql-validation-autocomplete.esql.definitions.tanhDoc": "正接双曲線関数。",
- "kbn-esql-validation-autocomplete.esql.definitions.tauDoc": "円の円周と半径の比率。",
- "kbn-esql-validation-autocomplete.esql.definitions.toBooleanDoc": "ブール値に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toCartesianPointDoc": "入力値をpoint値に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toCartesianshapeDoc": "入力値をcartesian_shape値に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toDateTimeDoc": "日付に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toDegreesDoc": "度に変換します",
- "kbn-esql-validation-autocomplete.esql.definitions.toDoubleDoc": "doubleに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toGeopointDoc": "geo_pointに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toGeoshapeDoc": "入力値をgeo_shape値に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toIntegerDoc": "整数に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toIpDoc": "IPに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toLongDoc": "longに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toLowerDoc": "小文字に変換された入力文字列を表す新しい文字列を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toRadiansDoc": "ラジアンに変換します",
- "kbn-esql-validation-autocomplete.esql.definitions.toStringDoc": "文字列に変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toUnsignedLongDoc": "符号なしlongに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toUpperDoc": "大文字に変換された入力文字列を表す新しい文字列を返します。",
- "kbn-esql-validation-autocomplete.esql.definitions.toVersionDoc": "バージョンに変換します。",
- "kbn-esql-validation-autocomplete.esql.definitions.trimDoc": "文字列から先頭と末尾の空白を削除します。",
"kbn-esql-validation-autocomplete.esql.definitions.whereDoc": "「predicate-expressions」を使用して、検索結果をフィルターします。予測式は評価時にTRUEまたはFALSEを返します。whereコマンドはTRUEに評価される結果のみを返します。たとえば、特定のフィールド値の結果をフィルターします",
"kbn-esql-validation-autocomplete.esql.definitions.withDoc": "を使用して",
"kbn-esql-validation-autocomplete.esql.quickfix.replaceWithQuote": "引用符を\"(二重引用符)に変更",
@@ -25507,7 +25437,6 @@
"xpack.ml.actions.clearSelectionTitle": "選択した項目をクリア",
"xpack.ml.actions.createADJobFromLens": "異常検知ジョブの作成",
"xpack.ml.actions.createADJobFromPatternAnalysis": "分類異常検知ジョブを作成",
- "xpack.ml.actions.editAnomalyChartsTitle": "異常グラフを編集",
"xpack.ml.actions.openInAnomalyExplorerTitle": "異常エクスプローラーで開く",
"xpack.ml.actions.runPatternAnalysis.description": "ユーザーがフィールドに対してパターン分析を実行する場合にトリガーされます。",
"xpack.ml.actions.runPatternAnalysis.title": "パターン分析を実行",
@@ -25716,7 +25645,6 @@
"xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel": "プロットする最大系列数",
"xpack.ml.anomalyChartsEmbeddable.panelTitleLabel": "パネルタイトル",
"xpack.ml.anomalyChartsEmbeddable.setupModal.cancelButtonLabel": "キャンセル",
- "xpack.ml.anomalyChartsEmbeddable.setupModal.confirmButtonLabel": "構成を確認",
"xpack.ml.anomalyChartsEmbeddable.setupModal.title": "異常エクスプローラーグラフ構成",
"xpack.ml.anomalyDetection.anomalyExplorer.docTitle": "異常エクスプローラー",
"xpack.ml.anomalyDetection.anomalyExplorerLabel": "異常エクスプローラー",
@@ -40957,7 +40885,6 @@
"xpack.synthetics.monitorList.viewInDiscover": "Discoverに表示",
"xpack.synthetics.monitorLocation.locationContextMenuTitleLabel": "場所に移動",
"xpack.synthetics.monitorLocation.locationLabel": "場所",
- "xpack.synthetics.monitorManagement.accessRestricted": "グローバルマネージドテストインフラストラクチャーへのアクセスは制限されています。",
"xpack.synthetics.monitorManagement.actions": "アクション",
"xpack.synthetics.monitorManagement.addAgentPolicyDesc": "非公開の場所にはエージェントポリシーが必要です。非公開の場所を追加するには、まず、Fleetでエージェントポリシーを作成する必要があります。",
"xpack.synthetics.monitorManagement.addEdit.createMonitorLabel": "監視の作成",
@@ -41016,10 +40943,8 @@
"xpack.synthetics.monitorManagement.getProjectApiKey.label": "プロジェクトAPIキーを生成",
"xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "プロジェクトAPIキーを生成",
"xpack.synthetics.monitorManagement.inProgress": "進行中",
- "xpack.synthetics.monitorManagement.label": "Syntheticsアプリ",
"xpack.synthetics.monitorManagement.learnMore": "詳細については、",
"xpack.synthetics.monitorManagement.learnMore.label": "詳細",
- "xpack.synthetics.monitorManagement.loading.label": "Syntheticsアプリを読み込み中",
"xpack.synthetics.monitorManagement.loadingSteps": "ステップを読み込んでいます...",
"xpack.synthetics.monitorManagement.locationName": "場所名",
"xpack.synthetics.monitorManagement.locationsLabel": "場所",
@@ -41057,7 +40982,6 @@
"xpack.synthetics.monitorManagement.projectDelete.docsLink": "詳細",
"xpack.synthetics.monitorManagement.projectPush.label": "プロジェクトプッシュコマンド",
"xpack.synthetics.monitorManagement.readDocs": "ドキュメントを読む",
- "xpack.synthetics.monitorManagement.requestAccess": "アクセスをリクエストする",
"xpack.synthetics.monitorManagement.saveLabel": "保存",
"xpack.synthetics.monitorManagement.selectOneOrMoreLocations": "1つ以上の場所を選択してください。",
"xpack.synthetics.monitorManagement.selectOneOrMoreLocationsDetails": "モニターが実行される場所を選択します。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 117cbce80da6c..d35e005f3bb61 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -5065,27 +5065,15 @@
"kbn-esql-validation-autocomplete.esql.definition.orDoc": "或",
"kbn-esql-validation-autocomplete.esql.definition.rlikeDoc": "根据字符串正则表达式筛选数据",
"kbn-esql-validation-autocomplete.esql.definition.subtractDoc": "减 (-)",
- "kbn-esql-validation-autocomplete.esql.definitions.absDoc": "返回绝对值。",
- "kbn-esql-validation-autocomplete.esql.definitions.acosDoc": "反余弦三角函数",
"kbn-esql-validation-autocomplete.esql.definitions.appendSeparatorDoc": "分隔已追加字段的字符。默认为空字符串 (\"\")。",
"kbn-esql-validation-autocomplete.esql.definitions.asDoc": "作为",
- "kbn-esql-validation-autocomplete.esql.definitions.asinDoc": "反正弦三角函数",
- "kbn-esql-validation-autocomplete.esql.definitions.atan2Doc": "笛卡儿平面中正 x 轴与从原点到点 (x , y) 构成的射线之间的角度",
- "kbn-esql-validation-autocomplete.esql.definitions.atanDoc": "反正切三角函数",
"kbn-esql-validation-autocomplete.esql.definitions.autoBucketDoc": "根据给定范围和存储桶目标自动收集存储桶日期。",
"kbn-esql-validation-autocomplete.esql.definitions.avgDoc": "返回字段中的值的平均值",
"kbn-esql-validation-autocomplete.esql.definitions.byDoc": "依据",
- "kbn-esql-validation-autocomplete.esql.definitions.caseDoc": "接受成对的条件和值。此函数返回属于第一个评估为 `true` 的条件的值。如果参数数量为奇数,则最后一个参数为在无条件匹配时返回的默认值。",
"kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc": "扩充在任何集群上发生",
"kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc": "扩充在接收 ES|QL 的协调集群上发生",
"kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc": "跨集群查询模式",
"kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc": "扩充在托管目标索引的集群上发生。",
- "kbn-esql-validation-autocomplete.esql.definitions.ceilDoc": "将数字四舍五入为最近的整数。",
- "kbn-esql-validation-autocomplete.esql.definitions.cidrMatchDoc": "此函数接受的第一个参数应为 IP 类型,后接一个或多个评估为 CIDR 规范的参数。",
- "kbn-esql-validation-autocomplete.esql.definitions.coalesceDoc": "返回第一个非 null 值。",
- "kbn-esql-validation-autocomplete.esql.definitions.concatDoc": "串联两个或多个字符串。",
- "kbn-esql-validation-autocomplete.esql.definitions.cosDoc": "余弦三角函数",
- "kbn-esql-validation-autocomplete.esql.definitions.coshDoc": "余弦双曲函数",
"kbn-esql-validation-autocomplete.esql.definitions.countDistinctDoc": "返回字段中不同值的计数。",
"kbn-esql-validation-autocomplete.esql.definitions.countDoc": "返回字段中的值的计数。",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.day": "天",
@@ -5104,87 +5092,29 @@
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.weeks": "周(复数)",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.year": "年",
"kbn-esql-validation-autocomplete.esql.definitions.dateDurationDefinition.years": "年(复数)",
- "kbn-esql-validation-autocomplete.esql.definitions.dateExtractDoc": "提取日期的某些部分,如年、月、日、小时。支持的字段类型为 java.time.temporal.ChronoField 提供的那些类型",
- "kbn-esql-validation-autocomplete.esql.definitions.dateFormatDoc": "以提供的格式返回日期的字符串表示形式。如果未指定格式,则使用“yyyy-MM-dd'T'HH:mm:ss.SSSZ”格式。",
- "kbn-esql-validation-autocomplete.esql.definitions.dateParseDoc": "解析字符串中的日期。",
- "kbn-esql-validation-autocomplete.esql.definitions.dateTruncDoc": "将日期向下舍入到最近的时间间隔。时间间隔可以用时间跨度文本语法表示。",
"kbn-esql-validation-autocomplete.esql.definitions.dissectDoc": "根据模式从单个字符串输入中提取多个字符串值",
"kbn-esql-validation-autocomplete.esql.definitions.dropDoc": "丢弃列",
- "kbn-esql-validation-autocomplete.esql.definitions.eDoc": "Euler 函数的编号。",
- "kbn-esql-validation-autocomplete.esql.definitions.endsWithDoc": "返回布尔值,指示关键字字符串是否以另一个字符串结尾:",
"kbn-esql-validation-autocomplete.esql.definitions.enrichDoc": "用其他表来扩充表。在使用扩充之前,您需要创建并执行扩充策略。",
"kbn-esql-validation-autocomplete.esql.definitions.evalDoc": "计算表达式并将生成的值置入搜索结果字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.floorDoc": "将数字向下舍入到最近的整数。",
"kbn-esql-validation-autocomplete.esql.definitions.fromDoc": "从一个或多个数据流、索引或别名中检索数据。在查询或子查询中,必须先使用 from 命令,并且它不需要前导管道符。例如,要从索引中检索数据:",
- "kbn-esql-validation-autocomplete.esql.definitions.greatestDoc": "返回许多列中的最大值。",
"kbn-esql-validation-autocomplete.esql.definitions.grokDoc": "根据模式从单个字符串输入中提取多个字符串值",
"kbn-esql-validation-autocomplete.esql.definitions.keepDoc": "通过在字段中应用 keep 子句重新安排输入表中的字段",
- "kbn-esql-validation-autocomplete.esql.definitions.leastDoc": "返回许多列中的最小值。",
- "kbn-esql-validation-autocomplete.esql.definitions.leftDoc": "返回从字符串中提取长度字符的子字符串,从左侧开始。",
- "kbn-esql-validation-autocomplete.esql.definitions.lengthDoc": "返回字符串的字符长度。",
"kbn-esql-validation-autocomplete.esql.definitions.limitDoc": "根据指定的“限制”按搜索顺序返回第一个搜索结果。",
- "kbn-esql-validation-autocomplete.esql.definitions.log10Doc": "返回对数底数 10。",
- "kbn-esql-validation-autocomplete.esql.definitions.logDoc": "如参数中所指定,标量函数 log(based, value) 返回特定底数的值的对数",
- "kbn-esql-validation-autocomplete.esql.definitions.ltrimDoc": "从字符串中移除前导空格。",
"kbn-esql-validation-autocomplete.esql.definitions.maxDoc": "返回字段中的最大值。",
"kbn-esql-validation-autocomplete.esql.definitions.medianDeviationDoc": "返回每个数据点的中位数与整个样例的中位数的偏差。",
"kbn-esql-validation-autocomplete.esql.definitions.medianDoc": "返回 50% 百分位数。",
"kbn-esql-validation-autocomplete.esql.definitions.metadataDoc": "元数据",
"kbn-esql-validation-autocomplete.esql.definitions.minDoc": "返回字段中的最小值。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvAvgDoc": "将多值字段转换为包含所有值的平均值的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvConcatDoc": "将多值字符串字段转换为单值字段,其中包含由分隔符分隔的所有值的串联形式",
- "kbn-esql-validation-autocomplete.esql.definitions.mvCountDoc": "将多值字段转换为包含值计数的单值字段",
- "kbn-esql-validation-autocomplete.esql.definitions.mvDedupeDoc": "移除多值字段中的重复项",
"kbn-esql-validation-autocomplete.esql.definitions.mvExpandDoc": "将多值字段扩展成每个值一行,从而复制其他字段",
- "kbn-esql-validation-autocomplete.esql.definitions.mvFirstDoc": "将多值字段缩减为包含第一个值的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvLastDoc": "将多值字段缩减为包含最后一个值的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMaxDoc": "将多值字段转换为包含最大值的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMedianDoc": "将多值字段转换为包含中位数值的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvMinDoc": "将多值字段转换为包含最小值的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.mvSumDoc": "将多值字段转换为包含所有值的总和的单值字段。",
- "kbn-esql-validation-autocomplete.esql.definitions.nowDoc": "返回当前日期和时间。",
"kbn-esql-validation-autocomplete.esql.definitions.onDoc": "开启",
"kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc": "返回字段的第 n 个百分位。",
- "kbn-esql-validation-autocomplete.esql.definitions.piDoc": "圆的周长与其直径的比率。",
- "kbn-esql-validation-autocomplete.esql.definitions.powDoc": "返回提升为幂(第二个参数)的底数(第一个参数)的值。",
"kbn-esql-validation-autocomplete.esql.definitions.renameDoc": "将旧列重命名为新列",
- "kbn-esql-validation-autocomplete.esql.definitions.replaceDoc": "此函数将字符串(第 1 个参数)中正则表达式(第 2 个参数)的任何匹配项替换为替代字符串(第 3 个参数)。如果任何参数为 NULL,则结果为 NULL。",
- "kbn-esql-validation-autocomplete.esql.definitions.rightDoc": "返回从字符串中提取长度字符的子字符串,从右侧开始。",
- "kbn-esql-validation-autocomplete.esql.definitions.roundDoc": "返回四舍五入到小数(由最近的整数值指定)的数字。默认做法是四舍五入到整数。",
"kbn-esql-validation-autocomplete.esql.definitions.rowDoc": "生成一个行,其中包含一个或多个含有您指定的值的列。这可以用于测试。",
- "kbn-esql-validation-autocomplete.esql.definitions.rtrimDoc": "从字符串中移除尾随空格。",
"kbn-esql-validation-autocomplete.esql.definitions.showDoc": "返回有关部署及其功能的信息",
- "kbn-esql-validation-autocomplete.esql.definitions.sinDoc": "正弦三角函数。",
- "kbn-esql-validation-autocomplete.esql.definitions.sinhDoc": "正弦双曲函数。",
"kbn-esql-validation-autocomplete.esql.definitions.sortDoc": "按指定字段对所有结果排序。默认情况下,会将 null 值视为大于任何其他值。使用升序排序顺序时,会最后对 null 值排序,而使用降序排序顺序时,会首先对 null 值排序。您可以通过提供 NULLS FIRST 或 NULLS LAST 来更改该排序",
- "kbn-esql-validation-autocomplete.esql.definitions.splitDoc": "将单值字符串拆分成多个字符串。",
- "kbn-esql-validation-autocomplete.esql.definitions.sqrtDoc": "返回数字的平方根。",
- "kbn-esql-validation-autocomplete.esql.definitions.startsWithDoc": "返回指示关键字字符串是否以另一个字符串开头的布尔值。",
"kbn-esql-validation-autocomplete.esql.definitions.statsDoc": "对传入的搜索结果集计算汇总统计信息,如平均值、计数和总和。与 SQL 聚合类似,如果使用不含 BY 子句的 stats 命令,则只返回一行内容,即聚合传入的整个搜索结果集。使用 BY 子句时,将为在 BY 子句中指定的字段中的每个不同值返回一行内容。stats 命令仅返回聚合中的字段,并且您可以将一系列统计函数与 stats 命令搭配在一起使用。执行多个聚合时,请用逗号分隔每个聚合。",
"kbn-esql-validation-autocomplete.esql.definitions.stCentroidDoc": "返回字段中不同值的计数。",
- "kbn-esql-validation-autocomplete.esql.definitions.substringDoc": "返回字符串的子字符串,用起始位置和可选长度指定。",
"kbn-esql-validation-autocomplete.esql.definitions.sumDoc": "返回字段中的值的总和。",
- "kbn-esql-validation-autocomplete.esql.definitions.tanDoc": "正切三角函数。",
- "kbn-esql-validation-autocomplete.esql.definitions.tanhDoc": "正切双曲函数。",
- "kbn-esql-validation-autocomplete.esql.definitions.tauDoc": "圆的圆周长与其半径的比率。",
- "kbn-esql-validation-autocomplete.esql.definitions.toBooleanDoc": "转换为布尔值。",
- "kbn-esql-validation-autocomplete.esql.definitions.toCartesianPointDoc": "将输入值转换为 `point` 值。",
- "kbn-esql-validation-autocomplete.esql.definitions.toCartesianshapeDoc": "将输入值转换为 cartesian_shape 值。",
- "kbn-esql-validation-autocomplete.esql.definitions.toDateTimeDoc": "转换为日期。",
- "kbn-esql-validation-autocomplete.esql.definitions.toDegreesDoc": "转换为度",
- "kbn-esql-validation-autocomplete.esql.definitions.toDoubleDoc": "转换为双精度值。",
- "kbn-esql-validation-autocomplete.esql.definitions.toGeopointDoc": "转换为 geo_point。",
- "kbn-esql-validation-autocomplete.esql.definitions.toGeoshapeDoc": "将输入值转换为 geo_shape 值。",
- "kbn-esql-validation-autocomplete.esql.definitions.toIntegerDoc": "转换为整数。",
- "kbn-esql-validation-autocomplete.esql.definitions.toIpDoc": "转换为 IP。",
- "kbn-esql-validation-autocomplete.esql.definitions.toLongDoc": "转换为长整型。",
- "kbn-esql-validation-autocomplete.esql.definitions.toLowerDoc": "返回一个新字符串,表示已将输入字符串转为小写。",
- "kbn-esql-validation-autocomplete.esql.definitions.toRadiansDoc": "转换为弧度",
- "kbn-esql-validation-autocomplete.esql.definitions.toStringDoc": "转换为字符串。",
- "kbn-esql-validation-autocomplete.esql.definitions.toUnsignedLongDoc": "转换为无符号长整型。",
- "kbn-esql-validation-autocomplete.esql.definitions.toUpperDoc": "返回一个新字符串,表示已将输入字符串转为大写。",
- "kbn-esql-validation-autocomplete.esql.definitions.toVersionDoc": "转换为版本。",
- "kbn-esql-validation-autocomplete.esql.definitions.trimDoc": "从字符串中移除前导和尾随空格。",
"kbn-esql-validation-autocomplete.esql.definitions.whereDoc": "使用“predicate-expressions”可筛选搜索结果。进行计算时,谓词表达式将返回 TRUE 或 FALSE。where 命令仅返回计算结果为 TRUE 的结果。例如,筛选特定字段值的结果",
"kbn-esql-validation-autocomplete.esql.definitions.withDoc": "具有",
"kbn-esql-validation-autocomplete.esql.quickfix.replaceWithQuote": "将引号更改为 \"(双引号)",
@@ -25546,7 +25476,6 @@
"xpack.ml.actions.clearSelectionTitle": "清除所选内容",
"xpack.ml.actions.createADJobFromLens": "创建异常检测作业",
"xpack.ml.actions.createADJobFromPatternAnalysis": "创建归类异常检测作业",
- "xpack.ml.actions.editAnomalyChartsTitle": "编辑异常图表",
"xpack.ml.actions.openInAnomalyExplorerTitle": "在 Anomaly Explorer 中打开",
"xpack.ml.actions.runPatternAnalysis.description": "在用户希望对字段运行模式分析时触发。",
"xpack.ml.actions.runPatternAnalysis.title": "运行模式分析",
@@ -25755,7 +25684,6 @@
"xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel": "要绘制的最大序列数目",
"xpack.ml.anomalyChartsEmbeddable.panelTitleLabel": "面板标题",
"xpack.ml.anomalyChartsEmbeddable.setupModal.cancelButtonLabel": "取消",
- "xpack.ml.anomalyChartsEmbeddable.setupModal.confirmButtonLabel": "确认配置",
"xpack.ml.anomalyChartsEmbeddable.setupModal.title": "异常浏览器图表配置",
"xpack.ml.anomalyDetection.anomalyExplorer.docTitle": "Anomaly Explorer",
"xpack.ml.anomalyDetection.anomalyExplorerLabel": "Anomaly Explorer",
@@ -41004,7 +40932,6 @@
"xpack.synthetics.monitorList.viewInDiscover": "在 Discover 中查看",
"xpack.synthetics.monitorLocation.locationContextMenuTitleLabel": "前往位置",
"xpack.synthetics.monitorLocation.locationLabel": "位置",
- "xpack.synthetics.monitorManagement.accessRestricted": "您对全球托管测试基础设施的访问权限受到限制。",
"xpack.synthetics.monitorManagement.actions": "操作",
"xpack.synthetics.monitorManagement.addAgentPolicyDesc": "专用位置需要代理策略。要添加专用位置,必须首先在 Fleet 中创建代理策略。",
"xpack.synthetics.monitorManagement.addEdit.createMonitorLabel": "创建监测",
@@ -41063,10 +40990,8 @@
"xpack.synthetics.monitorManagement.getProjectApiKey.label": "生成项目 API 密钥",
"xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "生成项目 API 密钥",
"xpack.synthetics.monitorManagement.inProgress": "进行中",
- "xpack.synthetics.monitorManagement.label": "Synthetics 应用",
"xpack.synthetics.monitorManagement.learnMore": "有关更多信息,",
"xpack.synthetics.monitorManagement.learnMore.label": "了解详情",
- "xpack.synthetics.monitorManagement.loading.label": "正在加载 Synthetics 应用",
"xpack.synthetics.monitorManagement.loadingSteps": "正在加载步骤......",
"xpack.synthetics.monitorManagement.locationName": "位置名称",
"xpack.synthetics.monitorManagement.locationsLabel": "位置",
@@ -41104,7 +41029,6 @@
"xpack.synthetics.monitorManagement.projectDelete.docsLink": "了解详情",
"xpack.synthetics.monitorManagement.projectPush.label": "项目推送命令",
"xpack.synthetics.monitorManagement.readDocs": "阅读文档",
- "xpack.synthetics.monitorManagement.requestAccess": "请求访问权限",
"xpack.synthetics.monitorManagement.saveLabel": "保存",
"xpack.synthetics.monitorManagement.selectOneOrMoreLocations": "选择一个或多个位置。",
"xpack.synthetics.monitorManagement.selectOneOrMoreLocationsDetails": "选择将执行监测的位置。",
diff --git a/x-pack/test/accessibility/apps/group3/ml_embeddables_in_dashboard.ts b/x-pack/test/accessibility/apps/group3/ml_embeddables_in_dashboard.ts
index 39a10547c57b9..69a95822be3af 100644
--- a/x-pack/test/accessibility/apps/group3/ml_embeddables_in_dashboard.ts
+++ b/x-pack/test/accessibility/apps/group3/ml_embeddables_in_dashboard.ts
@@ -103,12 +103,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('can select jobs', async () => {
- await ml.dashboardJobSelectionTable.setRowCheckboxState(testData.jobConfig.job_id, true);
- await ml.dashboardJobSelectionTable.applyJobSelection();
+ await ml.alerting.selectJobs([testData.jobConfig.job_id]);
+ await ml.alerting.assertJobSelection([testData.jobConfig.job_id]);
+ });
+
+ it('populates with default default info', async () => {
await ml.dashboardEmbeddables.assertAnomalyChartsEmbeddableInitializerExists();
+ await ml.dashboardEmbeddables.assertSelectMaxSeriesToPlotValue(6);
await a11y.testAppSnapshot();
});
-
it('create new anomaly charts panel', async () => {
await ml.dashboardEmbeddables.clickInitializerConfirmButtonEnabled();
await ml.dashboardEmbeddables.assertDashboardPanelExists(testData.panelTitle);
diff --git a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts
index cbefc039ca296..5f6d693dfb551 100644
--- a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts
+++ b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts
@@ -96,6 +96,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: false,
isEnabled: false,
isValidApiKey: false,
+ isServiceAllowed: true,
});
} finally {
await security.user.delete(username);
@@ -142,6 +143,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
@@ -190,6 +192,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
const validApiKeys = await getApiKeys();
@@ -209,6 +212,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
const validApiKeys2 = await getApiKeys();
@@ -292,6 +296,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
const validApiKeys2 = await getApiKeys();
@@ -341,6 +346,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
const validApiKeys = await getApiKeys();
@@ -371,6 +377,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
const validApiKeys2 = await getApiKeys();
@@ -417,6 +424,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: false,
isEnabled: false,
isValidApiKey: false,
+ isServiceAllowed: true,
});
} finally {
await security.role.delete(roleName);
@@ -483,6 +491,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
} finally {
await security.user.delete(username);
@@ -533,6 +542,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: false,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
} finally {
await supertestWithAuth
@@ -586,6 +596,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
await supertest
@@ -610,6 +621,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
// can disable synthetics in non default space when enabled in default space
@@ -635,6 +647,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: true,
});
} finally {
await security.user.delete(username);
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts
index 19b48858b115b..7f13c22c9eb91 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts
@@ -73,10 +73,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
ML_EMBEDDABLE_TYPES.ANOMALY_CHARTS
);
});
-
it('can select jobs', async () => {
- await ml.dashboardJobSelectionTable.setRowCheckboxState(testData.jobConfig.job_id, true);
- await ml.dashboardJobSelectionTable.applyJobSelection();
+ await ml.alerting.selectJobs([testData.jobConfig.job_id]);
+ await ml.alerting.assertJobSelection([testData.jobConfig.job_id]);
+ });
+
+ it('populates with default default info', async () => {
await ml.dashboardEmbeddables.assertAnomalyChartsEmbeddableInitializerExists();
await ml.dashboardEmbeddables.assertSelectMaxSeriesToPlotValue(6);
});
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts
index 254bd76f5616b..a2b199092cdc8 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/index.ts
@@ -40,6 +40,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./single_metric_job'));
if (!isCcs) {
+ loadTestFile(require.resolve('./job_expanded_details'));
loadTestFile(require.resolve('./single_metric_job_without_datafeed_start'));
loadTestFile(require.resolve('./multi_metric_job'));
loadTestFile(require.resolve('./population_job'));
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts
new file mode 100644
index 0000000000000..c1c8eee13ba22
--- /dev/null
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts
@@ -0,0 +1,117 @@
+/*
+ * 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 { Job } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs';
+import { JOB_STATE } from '@kbn/ml-plugin/common';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+import { QuickFilterButtonTypes } from '../../../services/ml/job_table';
+
+export default function ({ getService }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const ml = getService('ml');
+
+ const jobId = `fq_single_1_job_list_test_${Date.now()}`;
+ const calendarId = `test-calendar-${Date.now()}`;
+ const jobsSummaryId = `jobs_summary_fq_multi_${Date.now()}`;
+
+ const multiMetricJobConfig: Job = {
+ allow_lazy_open: false,
+ model_snapshot_retention_days: 0,
+ results_index_name: '',
+ job_id: jobsSummaryId,
+ description: 'mean(responsetime) partition=airline on farequote dataset with 1h bucket span',
+ groups: ['farequote', 'automated', 'multi-metric'],
+ analysis_config: {
+ bucket_span: '1h',
+ influencers: ['airline'],
+ detectors: [
+ { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' },
+ ],
+ },
+ data_description: { time_field: '@timestamp' },
+ analysis_limits: { model_memory_limit: '50mb' },
+ model_plot_config: { enabled: true },
+ };
+
+ describe('job expanded details', function () {
+ this.tags(['ml']);
+
+ before(async () => {
+ await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
+ await ml.testResources.createDataViewIfNeeded('ft_farequote', '@timestamp');
+ await ml.testResources.setKibanaTimeZoneToUTC();
+
+ const JOB_CONFIG = ml.commonConfig.getADFqSingleMetricJobConfig(jobId);
+ const DATAFEED_CONFIG = ml.commonConfig.getADFqDatafeedConfig(jobId);
+
+ await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG);
+
+ await ml.api.waitForJobState(jobId, JOB_STATE.CLOSED);
+ await ml.api.openAnomalyDetectionJob(jobId);
+
+ await ml.api.addForecast(jobId, { duration: '1d' });
+
+ await ml.api.assertForecastResultsExist(jobId);
+
+ await ml.api.createAnomalyDetectionJob(multiMetricJobConfig);
+
+ await ml.api.createCalendar(calendarId, {
+ description: 'calendar for job list test',
+ job_ids: [jobId],
+ });
+
+ await ml.navigation.navigateToMl();
+
+ await ml.navigation.navigateToJobManagement();
+ });
+
+ after(async () => {
+ await ml.api.closeAnomalyDetectionJob(jobId);
+ await ml.api.deleteAnomalyDetectionJobES(jobId);
+
+ await ml.api.deleteAnomalyDetectionJobES(jobsSummaryId);
+
+ await ml.api.cleanMlIndices();
+ await ml.testResources.deleteDataViewByTitle('ft_farequote');
+ });
+
+ it('expanded row with connected calendar shows link to calendar', async () => {
+ await ml.jobExpandedDetails.assertJobRowCalendars(jobId, [calendarId]);
+ });
+
+ it('expanded row with forecast should display open forecast button', async () => {
+ await ml.jobExpandedDetails.assertForecastElements(jobId);
+ });
+
+ it('expanded row with annotations can be edited', async () => {
+ const annotationsFromApi = await ml.api.getAnnotations(jobId);
+
+ await ml.jobExpandedDetails.editAnnotation(jobId, 'edited annotation', annotationsFromApi);
+ });
+
+ it('expanded row with data feed flyout should display correctly', async () => {
+ await ml.jobExpandedDetails.assertDataFeedFlyout(jobId);
+ });
+
+ it('expanded row with model snapshot should display correctly', async () => {
+ await ml.jobExpandedDetails.assertModelSnapshotManagement(jobId);
+ });
+
+ it('multi-selection with one opened job should only present the opened job when job list is filtered by the Opened button', async () => {
+ await ml.jobTable.selectAllJobs();
+ await ml.jobExpandedDetails.assertJobListMultiSelectionText('2 jobs selected');
+ await ml.jobTable.filterByState(QuickFilterButtonTypes.Opened);
+ await ml.jobTable.assertJobsInTable([jobId]);
+ await ml.jobExpandedDetails.assertJobListMultiSelectionText('1 job selected');
+ });
+
+ it('multi-selection with one closed job should only present the closed job when job list is filtered by the Closed button', async () => {
+ await ml.jobTable.filterByState(QuickFilterButtonTypes.Closed);
+ await ml.jobTable.assertJobsInTable([jobsSummaryId]);
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts
index 8b29adf3d5384..957ac090e1ade 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts
@@ -388,5 +388,90 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.api.assertNoJobResultsExist(jobIdClone);
});
+
+ it('job cloning with too short of a job creation time range results in validation callouts', async () => {
+ await ml.testExecution.logTestStep('job cloning loads the job management page');
+ await ml.navigation.navigateToMl();
+ await ml.navigation.navigateToJobManagement();
+
+ await ml.testExecution.logTestStep(`cloning job: [${jobId}]`);
+ await ml.jobTable.clickCloneJobAction(jobId);
+
+ await ml.testExecution.logTestStep('job cloning displays the time range step');
+ await ml.jobWizardCommon.assertTimeRangeSectionExists();
+
+ await ml.testExecution.logTestStep('job cloning sets the time range');
+ await ml.jobWizardCommon.clickUseFullDataButton(
+ 'Feb 7, 2016 @ 00:00:00.000',
+ 'Feb 11, 2016 @ 23:59:54.000'
+ );
+
+ await ml.jobWizardCommon.goToTimeRangeStep();
+
+ const { startDate: origStartDate } = await ml.jobWizardCommon.getSelectedDateRange();
+
+ await ml.testExecution.logTestStep('calculate the new end date');
+ const shortDurationEndDate: string = `${origStartDate?.split(':', 1)[0]}:01:00.000`;
+
+ await ml.testExecution.logTestStep('set the new end date');
+ await ml.jobWizardCommon.setTimeRange({ endTime: shortDurationEndDate });
+
+ // assert time is set as expected
+ await ml.jobWizardCommon.assertDateRangeSelection(
+ origStartDate as string,
+ shortDurationEndDate
+ );
+
+ await ml.jobWizardCommon.advanceToPickFieldsSection();
+ await ml.jobWizardCommon.advanceToJobDetailsSection();
+ await ml.jobWizardCommon.assertJobIdInputExists();
+ await ml.jobWizardCommon.setJobId(`${jobIdClone}-again`);
+ await ml.jobWizardCommon.advanceToValidationSection();
+ await ml.jobWizardCommon.assertValidationCallouts([
+ 'mlValidationCallout warning',
+ 'mlValidationCallout error',
+ ]);
+ await ml.jobWizardCommon.assertCalloutText(
+ 'mlValidationCallout warning',
+ /Time range\s*The selected or available time range might be too short/
+ );
+
+ await ml.jobWizardCommon.goToTimeRangeStep();
+ await ml.jobWizardCommon.clickUseFullDataButton(
+ 'Feb 7, 2016 @ 00:00:00.000',
+ 'Feb 11, 2016 @ 23:59:54.000'
+ );
+ await ml.jobWizardCommon.goToValidationStep();
+ await ml.jobWizardCommon.assertValidationCallouts(['mlValidationCallout success']);
+ await ml.jobWizardCommon.assertCalloutText(
+ 'mlValidationCallout success',
+ /Time range\s*Valid and long enough to model patterns in the data/
+ );
+ });
+
+ it('job creation and toggling model change annotation triggers enable annotation recommendation callout', async () => {
+ await ml.jobWizardCommon.goToJobDetailsStep();
+ await ml.jobWizardCommon.ensureAdvancedSectionOpen();
+
+ await ml.commonUI.toggleSwitchIfNeeded('mlJobWizardSwitchAnnotations', false);
+ await ml.jobWizardCommon.assertAnnotationRecommendationCalloutVisible();
+
+ await ml.commonUI.toggleSwitchIfNeeded('mlJobWizardSwitchAnnotations', true);
+ await ml.jobWizardCommon.assertAnnotationRecommendationCalloutVisible(false);
+ });
+
+ it('job creation memory limit too large results in validation callout', async () => {
+ await ml.jobWizardCommon.goToJobDetailsStep();
+
+ const tooLarge = '100000000MB';
+ await ml.jobWizardCommon.setModelMemoryLimit(tooLarge);
+
+ await ml.jobWizardCommon.advanceToValidationSection();
+ await ml.jobWizardCommon.assertValidationCallouts(['mlValidationCallout warning']);
+ await ml.jobWizardCommon.assertCalloutText(
+ 'mlValidationCallout warning',
+ /Job will not be able to run in the current cluster because model memory limit is higher than/
+ );
+ });
});
}
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts
index 549757883101f..c323356c73e6e 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts
@@ -7,7 +7,6 @@
import type { Job, Datafeed } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs';
import type { AnomalySwimLaneEmbeddableState } from '@kbn/ml-plugin/public';
-import type { AnomalyChartsEmbeddableInput } from '@kbn/ml-plugin/public/embeddables';
import { stringHash } from '@kbn/ml-string-hash';
import type { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../services/ml/security_common';
@@ -521,7 +520,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const expectedAttachment = {
jobIds: [testData.jobConfig.job_id],
maxSeriesToPlot: 6,
- } as AnomalyChartsEmbeddableInput;
+ };
// @ts-expect-error Setting id to be undefined here
// since time range expected is of the chart plotEarliest/plotLatest, not of the global time range
diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts
index fdbbad01d003b..452abe6a54ea5 100644
--- a/x-pack/test/functional/services/ml/api.ts
+++ b/x-pack/test/functional/services/ml/api.ts
@@ -212,6 +212,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
return body.hits.hits.length > 0;
},
+ async addForecast(jobId: string, requestBody: { duration: string }): Promise {
+ log.debug(`Creating forecast for ${jobId}...`);
+ const { body, status } = await kbnSupertest
+ .post(`/internal/ml/anomaly_detectors/${jobId}/_forecast`)
+ .set(getCommonRequestHeader('1'))
+ .send(requestBody);
+ this.assertResponseStatusCode(200, status, body);
+ log.debug(`> Forecast for ${jobId} created`);
+ },
+
async assertForecastResultsExist(jobId: string) {
await retry.waitForWithTimeout(
`forecast results for job ${jobId} to exist`,
diff --git a/x-pack/test/functional/services/ml/cases.ts b/x-pack/test/functional/services/ml/cases.ts
index 016ffcb8486de..6c481df8b99a8 100644
--- a/x-pack/test/functional/services/ml/cases.ts
+++ b/x-pack/test/functional/services/ml/cases.ts
@@ -7,7 +7,6 @@
import type { SwimlaneType } from '@kbn/ml-plugin/public/application/explorer/explorer_constants';
import type { AnomalySwimLaneEmbeddableState } from '@kbn/ml-plugin/public';
-import type { AnomalyChartsEmbeddableInput } from '@kbn/ml-plugin/public/embeddables';
import type { FtrProviderContext } from '../../ftr_provider_context';
import type { MlAnomalySwimLane } from './swim_lane';
import type { MlAnomalyCharts } from './anomaly_charts';
@@ -71,7 +70,7 @@ export function MachineLearningCasesProvider(
async assertCaseWithAnomalyChartsAttachment(
params: CaseParams,
- attachment: AnomalyChartsEmbeddableInput,
+ attachment: { id?: string; jobIds: string[]; maxSeriesToPlot: number },
expectedChartsCount: number
) {
await this.assertBasicCaseProps(params);
diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts
index ef2605a716dbf..282ae1aba5033 100644
--- a/x-pack/test/functional/services/ml/common_ui.ts
+++ b/x-pack/test/functional/services/ml/common_ui.ts
@@ -447,5 +447,9 @@ export function MachineLearningCommonUIProvider({
await testSubjects.missingOrFail(selector);
}
},
+
+ async toggleSwitchIfNeeded(testSubj: string, targetState: boolean) {
+ await testSubjects.setEuiSwitch(testSubj, targetState ? 'check' : 'uncheck');
+ },
};
}
diff --git a/x-pack/test/functional/services/ml/dashboard_embeddables.ts b/x-pack/test/functional/services/ml/dashboard_embeddables.ts
index cf403bab147d8..9a5428276479e 100644
--- a/x-pack/test/functional/services/ml/dashboard_embeddables.ts
+++ b/x-pack/test/functional/services/ml/dashboard_embeddables.ts
@@ -117,19 +117,19 @@ export function MachineLearningDashboardEmbeddablesProvider(
async openAnomalyJobSelectionFlyout(
mlEmbeddableType: 'ml_anomaly_swimlane' | 'ml_anomaly_charts' | 'ml_single_metric_viewer'
) {
+ const name = {
+ ml_anomaly_swimlane: 'Anomaly swim lane',
+ ml_single_metric_viewer: 'Single metric viewer',
+ ml_anomaly_charts: 'Anomaly chart',
+ };
await retry.tryForTime(60 * 1000, async () => {
await dashboardAddPanel.clickEditorMenuButton();
await testSubjects.existOrFail('dashboardEditorContextMenu', { timeout: 2000 });
await dashboardAddPanel.clickEmbeddableFactoryGroupButton('ml');
- if (mlEmbeddableType === 'ml_single_metric_viewer') {
- await dashboardAddPanel.clickAddNewPanelFromUIActionLink('Single metric viewer');
- await testSubjects.existOrFail('mlAnomalyJobSelectionControls', { timeout: 2000 });
- } else {
- await dashboardAddPanel.clickAddNewEmbeddableLink(mlEmbeddableType);
- await mlDashboardJobSelectionTable.assertJobSelectionTableExists();
- }
+ await dashboardAddPanel.clickAddNewPanelFromUIActionLink(name[mlEmbeddableType]);
+ await testSubjects.existOrFail('mlAnomalyJobSelectionControls', { timeout: 2000 });
});
},
};
diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
index 50d7738abf732..8e0c1e84b4f5d 100644
--- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
+++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
@@ -553,12 +553,12 @@ export function MachineLearningDataFrameAnalyticsCreationProvider(
async assertValidationCalloutsExists() {
await retry.tryForTime(4000, async () => {
- await testSubjects.existOrFail('mlValidationCallout');
+ await testSubjects.existOrFail('~mlValidationCallout');
});
},
async assertAllValidationCalloutsPresent(expectedNumCallouts: number) {
- const validationCallouts = await testSubjects.findAll('mlValidationCallout');
+ const validationCallouts = await testSubjects.findAll('~mlValidationCallout');
expect(validationCallouts.length).to.eql(expectedNumCallouts);
},
diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts
index a2a5315bf5c50..4f25fa0808976 100644
--- a/x-pack/test/functional/services/ml/index.ts
+++ b/x-pack/test/functional/services/ml/index.ts
@@ -30,6 +30,7 @@ import { MachineLearningJobManagementProvider } from './job_management';
import { MachineLearningJobSelectionProvider } from './job_selection';
import { MachineLearningJobSourceSelectionProvider } from './job_source_selection';
import { MachineLearningJobTableProvider } from './job_table';
+import { MachineLearningJobExpandedDetailsProvider } from './job_expanded_details';
import { MachineLearningJobTypeSelectionProvider } from './job_type_selection';
import { MachineLearningJobWizardAdvancedProvider } from './job_wizard_advanced';
import { MachineLearningJobWizardCommonProvider } from './job_wizard_common';
@@ -122,6 +123,12 @@ export function MachineLearningProvider(context: FtrProviderContext) {
const jobSelection = MachineLearningJobSelectionProvider(context);
const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context);
const jobTable = MachineLearningJobTableProvider(context, commonUI, customUrls);
+
+ const jobExpandedDetails = MachineLearningJobExpandedDetailsProvider(
+ context,
+ jobTable,
+ jobAnnotations
+ );
const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context);
const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, commonUI);
const jobWizardCategorization = MachineLearningJobWizardCategorizationProvider(
@@ -198,6 +205,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
dataVisualizerTable,
forecast,
jobAnnotations,
+ jobExpandedDetails,
jobManagement,
jobSelection,
jobSourceSelection,
diff --git a/x-pack/test/functional/services/ml/job_annotations_table.ts b/x-pack/test/functional/services/ml/job_annotations_table.ts
index 89824378e7619..7945abd7e9608 100644
--- a/x-pack/test/functional/services/ml/job_annotations_table.ts
+++ b/x-pack/test/functional/services/ml/job_annotations_table.ts
@@ -7,7 +7,9 @@
import expect from '@kbn/expect';
+import { ProvidedType } from '@kbn/test';
import { FtrProviderContext } from '../../ftr_provider_context';
+export type MlJobAnnotations = ProvidedType;
export function MachineLearningJobAnnotationsProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
diff --git a/x-pack/test/functional/services/ml/job_expanded_details.ts b/x-pack/test/functional/services/ml/job_expanded_details.ts
new file mode 100644
index 0000000000000..44e8cb9c09bfd
--- /dev/null
+++ b/x-pack/test/functional/services/ml/job_expanded_details.ts
@@ -0,0 +1,151 @@
+/*
+ * 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 expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../ftr_provider_context';
+import { MlADJobTable } from './job_table';
+import { MlJobAnnotations } from './job_annotations_table';
+
+export function MachineLearningJobExpandedDetailsProvider(
+ { getService, getPageObject }: FtrProviderContext,
+ jobTable: MlADJobTable,
+ jobAnnotationsTable: MlJobAnnotations
+) {
+ const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+ const headerPage = getPageObject('header');
+
+ return {
+ async clickEditAnnotationAction(jobId: string, annotationId: string) {
+ await jobAnnotationsTable.ensureAnnotationsActionsMenuOpen(annotationId);
+ await testSubjects.click('mlAnnotationsActionEdit');
+ await testSubjects.existOrFail('mlAnnotationFlyout', {
+ timeout: 3_000,
+ });
+ },
+
+ async assertJobRowCalendars(
+ jobId: string,
+ expectedCalendars: string[],
+ checkForExists: boolean = true
+ ): Promise {
+ await jobTable.withDetailsOpen(jobId, async function verifyJobRowCalendars(): Promise {
+ for await (const expectedCalendar of expectedCalendars) {
+ const calendarSelector = `mlJobDetailsCalendar-${expectedCalendar}`;
+ await testSubjects[checkForExists ? 'existOrFail' : 'missingOrFail'](calendarSelector, {
+ timeout: 3_000,
+ });
+ if (checkForExists)
+ expect(await testSubjects.getVisibleText(calendarSelector)).to.be(expectedCalendar);
+ }
+ });
+ },
+
+ async assertForecastElements(jobId: string): Promise {
+ await jobTable.ensureDetailsOpen(jobId);
+ await this.openForecastTab(jobId);
+ await testSubjects.existOrFail('mlJobListForecastTabOpenSingleMetricViewButton', {
+ timeout: 5_000,
+ });
+ },
+
+ async clearSearchButton() {
+ if (await testSubjects.exists('clearSearchButton')) {
+ await testSubjects.click('clearSearchButton');
+ await testSubjects.missingOrFail('clearSearchButton');
+ }
+ },
+
+ async editAnnotation(
+ jobId: string,
+ newAnnotationText: string,
+ annotationsFromApi: any
+ ): Promise {
+ const length = annotationsFromApi.length;
+ expect(length).to.eql(
+ 1,
+ `Expect annotions from api to have length of 1, but got [${length}]`
+ );
+
+ await jobTable.ensureDetailsOpen(jobId);
+ await jobTable.openAnnotationsTab(jobId);
+ await this.clearSearchButton();
+
+ const { _id: annotationId }: { _id: string } = annotationsFromApi[0];
+
+ await this.clickEditAnnotationAction(jobId, annotationId);
+
+ await testSubjects.setValue('mlAnnotationsFlyoutTextInput', newAnnotationText, {
+ clearWithKeyboard: true,
+ });
+ await testSubjects.click('annotationFlyoutUpdateOrCreateButton');
+ await testSubjects.missingOrFail('mlAnnotationsFlyoutTextInput');
+ await jobTable.ensureDetailsClosed(jobId);
+
+ await jobTable.withDetailsOpen(jobId, async () => {
+ await jobTable.openAnnotationsTab(jobId);
+ await this.clearSearchButton();
+ const visibleText = await testSubjects.getVisibleText(
+ jobTable.detailsSelector(jobId, 'mlAnnotationsColumnAnnotation')
+ );
+ expect(visibleText).to.be(newAnnotationText);
+ });
+ },
+
+ async assertDataFeedFlyout(jobId: string): Promise {
+ await jobTable.withDetailsOpen(jobId, async () => {
+ await jobTable.openAnnotationsTab(jobId);
+ await this.clearSearchButton();
+ await testSubjects.click(jobTable.detailsSelector(jobId, 'euiCollapsedItemActionsButton'));
+ await testSubjects.click('mlAnnotationsActionViewDatafeed');
+ await testSubjects.existOrFail('mlAnnotationsViewDatafeedFlyoutChart');
+ const visibleText = await testSubjects.getVisibleText(
+ 'mlAnnotationsViewDatafeedFlyoutTitle'
+ );
+ expect(visibleText).to.be(`Datafeed chart for ${jobId}`);
+ await testSubjects.click('euiFlyoutCloseButton');
+ });
+ },
+
+ async openForecastTab(jobId: string) {
+ await retry.tryForTime(60_000, async () => {
+ await jobTable.ensureDetailsOpen(jobId);
+ await testSubjects.click(jobTable.detailsSelector(jobId, 'mlJobListTab-forecasts'), 3_000);
+ await headerPage.waitUntilLoadingHasFinished();
+ await this.assertJobDetailsTabOpen('mlJobListTab-forecasts');
+ });
+ },
+
+ async assertJobDetailsTabOpen(tabSubj: string) {
+ const isSelected = await testSubjects.getAttribute(tabSubj, 'aria-selected');
+ expect(isSelected).to.eql(
+ 'true',
+ `Expected job details tab [${tabSubj}] to be open, got: isSelected=[${isSelected}]`
+ );
+ },
+
+ async openModelSnapshotTab(jobId: string) {
+ await jobTable.ensureDetailsOpen(jobId);
+ await testSubjects.click(jobTable.detailsSelector(jobId, 'mlJobListTab-modelSnapshots'));
+ await this.assertJobDetailsTabOpen('mlJobListTab-modelSnapshots');
+ },
+
+ async assertModelSnapshotManagement(jobId: string): Promise {
+ await jobTable.withDetailsOpen(jobId, async () => {
+ await this.openModelSnapshotTab(jobId);
+ await testSubjects.existOrFail('mlADModelSnapShotRevertButton');
+ await testSubjects.existOrFail('mlADModelSnapShotsEditButton');
+ });
+ },
+
+ async assertJobListMultiSelectionText(expectedMsg: string): Promise {
+ const visibleText = await testSubjects.getVisibleText('~mlADJobListMultiSelectActionsArea');
+ expect(visibleText).to.be(expectedMsg);
+ },
+ };
+}
diff --git a/x-pack/test/functional/services/ml/job_table.ts b/x-pack/test/functional/services/ml/job_table.ts
index 9e1e72cdc4f1f..97ce1858bc2f1 100644
--- a/x-pack/test/functional/services/ml/job_table.ts
+++ b/x-pack/test/functional/services/ml/job_table.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { ProvidedType } from '@kbn/test';
-
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import {
TimeRangeType,
TIME_RANGE_TYPE,
@@ -40,6 +40,12 @@ export interface OtherUrlConfig {
url: string;
}
+export enum QuickFilterButtonTypes {
+ Opened = 'Opened',
+ Closed = 'Closed',
+ Started = 'Started',
+ Stopped = 'Stopped',
+}
export function MachineLearningJobTableProvider(
{ getPageObject, getService }: FtrProviderContext,
mlCommonUI: MlCommonUI,
@@ -50,6 +56,38 @@ export function MachineLearningJobTableProvider(
const retry = getService('retry');
return new (class MlJobTable {
+ public async selectAllJobs(): Promise {
+ await testSubjects.click('checkboxSelectAll');
+ }
+
+ public async assertJobsInTable(expectedJobIds: string[]) {
+ const sortedExpectedIds = expectedJobIds.sort();
+ const sortedActualJobIds = (await this.parseJobTable()).map((row) => row.id).sort();
+ expect(sortedActualJobIds).to.eql(
+ sortedExpectedIds,
+ `Expected jobs in table to be [${sortedExpectedIds}], got [${sortedActualJobIds}]`
+ );
+ }
+
+ public async filterByState(quickFilterButton: QuickFilterButtonTypes): Promise {
+ const searchBar: WebElementWrapper = await testSubjects.find('mlJobListSearchBar');
+ const quickFilter: WebElementWrapper = await searchBar.findByCssSelector(
+ `span[data-text="${quickFilterButton}"]`
+ );
+ await quickFilter.click();
+
+ const searchBarButtons = await searchBar.findAllByTagName('button');
+ let pressedBttnText: string = '';
+ for await (const button of searchBarButtons)
+ if ((await button.getAttribute('aria-pressed')) === 'true')
+ pressedBttnText = await button.getVisibleText();
+
+ expect(pressedBttnText).to.eql(
+ quickFilterButton,
+ `Expected visible text of pressed quick filter button to equal [${quickFilterButton}], but got [${pressedBttnText}]`
+ );
+ }
+
public async clickJobRowCalendarWithAssertion(
jobId: string,
calendarId: string
diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts
index e1e5b4ea0d45b..b1671626f191f 100644
--- a/x-pack/test/functional/services/ml/job_wizard_common.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_common.ts
@@ -26,6 +26,7 @@ export function MachineLearningJobWizardCommonProvider(
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const headerPage = getPageObject('header');
+ const browser = getService('browser');
function advancedSectionSelector(subSelector?: string) {
const subj = 'mlJobWizardAdvancedSection';
@@ -628,5 +629,78 @@ export function MachineLearningJobWizardCommonProvider(
await testSubjects.existOrFail(expectedSelector);
});
},
+
+ async assertAnnotationRecommendationCalloutVisible(expectVisible: boolean = true) {
+ const callOutTestSubj = 'mlJobWizardAlsoEnableAnnotationsRecommendationCallout';
+ if (expectVisible)
+ await testSubjects.existOrFail(callOutTestSubj, {
+ timeout: 3_000,
+ });
+ else
+ await testSubjects.missingOrFail(callOutTestSubj, {
+ timeout: 3_000,
+ });
+ },
+
+ async goToTimeRangeStep() {
+ await retry.tryForTime(60_000, async () => {
+ await testSubjects.existOrFail('mlJobWizardTimeRangeStep');
+ await testSubjects.click('mlJobWizardTimeRangeStep');
+ await this.assertTimeRangeSectionExists();
+ });
+ },
+
+ async goToValidationStep() {
+ await retry.tryForTime(60_000, async () => {
+ await testSubjects.existOrFail('mlJobWizardValidationStep');
+ await testSubjects.click('mlJobWizardValidationStep');
+ await this.assertValidationSectionExists();
+ });
+ },
+
+ async setTimeRange({ startTime, endTime }: { startTime?: string; endTime?: string }) {
+ const opts = {
+ clearWithKeyboard: true,
+ typeCharByChar: true,
+ };
+
+ if (startTime)
+ await testSubjects.setValue('mlJobWizardDatePickerRangeStartDate', startTime, opts);
+ if (endTime) await testSubjects.setValue('mlJobWizardDatePickerRangeEndDate', endTime, opts);
+
+ // escape popover
+ await browser.pressKeys(browser.keys.ESCAPE);
+ },
+
+ async goToJobDetailsStep() {
+ await testSubjects.existOrFail('mlJobWizardJobDetailsStep', {
+ timeout: 3_000,
+ });
+ await testSubjects.click('mlJobWizardJobDetailsStep');
+ await this.assertJobDetailsSectionExists();
+ },
+
+ async assertValidationCallouts(expectedCallOutSelectors: string[]) {
+ for await (const sel of expectedCallOutSelectors)
+ await testSubjects.existOrFail(sel, {
+ timeout: 3_000,
+ });
+ },
+
+ async assertCalloutText(calloutStatusTestSubj: string, expectedText: RegExp) {
+ const allCalloutStatusTexts = await testSubjects.getVisibleTextAll(calloutStatusTestSubj);
+
+ const oneCalloutMatches = allCalloutStatusTexts.some(
+ (visibleText) => !!visibleText.match(expectedText)
+ );
+ expect(oneCalloutMatches).to.eql(
+ true,
+ `Expect one of the callouts [${calloutStatusTestSubj}] to match [${expectedText}], instead found ${JSON.stringify(
+ allCalloutStatusTexts,
+ null,
+ 2
+ )}`
+ );
+ },
};
}
diff --git a/x-pack/test/spaces_api_integration/common/suites/create.ts b/x-pack/test/spaces_api_integration/common/suites/create.ts
index b882e328cc3a2..c7aab659ce960 100644
--- a/x-pack/test/spaces_api_integration/common/suites/create.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/create.ts
@@ -19,6 +19,7 @@ interface CreateTests {
newSpace: CreateTest;
alreadyExists: CreateTest;
reservedSpecified: CreateTest;
+ solutionSpecified: CreateTest;
}
interface CreateTestDefinition {
@@ -63,6 +64,17 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest) => {
+ expect(resp.body).to.eql({
+ id: 'solution',
+ name: 'space with solution',
+ description: 'a description',
+ color: '#5c5959',
+ disabledFeatures: [],
+ solution: 'search',
+ });
+ };
+
const makeCreateTest =
(describeFn: DescribeFn) =>
(description: string, { user = {}, spaceId, tests }: CreateTestDefinition) => {
@@ -128,6 +140,24 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest {
+ it(`should return ${tests.solutionSpecified.statusCode}`, async () => {
+ return supertest
+ .post(`${urlPrefix}/api/spaces/space`)
+ .auth(user.username, user.password)
+ .send({
+ name: 'space with solution',
+ id: 'solution',
+ description: 'a description',
+ color: '#5c5959',
+ solution: 'search',
+ disabledFeatures: [],
+ })
+ .expect(tests.solutionSpecified.statusCode)
+ .then(tests.solutionSpecified.response);
+ });
+ });
});
});
};
@@ -142,5 +172,6 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest {
@@ -68,6 +69,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 403,
response: expectRbacForbiddenResponse,
},
+ solutionSpecified: {
+ statusCode: 403,
+ response: expectRbacForbiddenResponse,
+ },
},
});
@@ -87,6 +92,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 200,
response: expectReservedSpecifiedResult,
},
+ solutionSpecified: {
+ statusCode: 200,
+ response: expectSolutionSpecifiedResult,
+ },
},
});
@@ -106,6 +115,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 200,
response: expectReservedSpecifiedResult,
},
+ solutionSpecified: {
+ statusCode: 200,
+ response: expectSolutionSpecifiedResult,
+ },
},
});
@@ -125,6 +138,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 200,
response: expectReservedSpecifiedResult,
},
+ solutionSpecified: {
+ statusCode: 200,
+ response: expectSolutionSpecifiedResult,
+ },
},
});
@@ -144,6 +161,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 403,
response: expectRbacForbiddenResponse,
},
+ solutionSpecified: {
+ statusCode: 403,
+ response: expectRbacForbiddenResponse,
+ },
},
});
@@ -163,6 +184,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 403,
response: expectRbacForbiddenResponse,
},
+ solutionSpecified: {
+ statusCode: 403,
+ response: expectRbacForbiddenResponse,
+ },
},
});
@@ -182,6 +207,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 403,
response: expectRbacForbiddenResponse,
},
+ solutionSpecified: {
+ statusCode: 403,
+ response: expectRbacForbiddenResponse,
+ },
},
});
@@ -201,6 +230,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 403,
response: expectRbacForbiddenResponse,
},
+ solutionSpecified: {
+ statusCode: 403,
+ response: expectRbacForbiddenResponse,
+ },
},
});
});
diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts
index 4d18c9b51dd55..000d6b8f2ebe7 100644
--- a/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts
+++ b/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts
@@ -19,6 +19,7 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
expectNewSpaceResult,
expectConflictResponse,
expectReservedSpecifiedResult,
+ expectSolutionSpecifiedResult,
} = createTestSuiteFactory(esArchiver, supertestWithoutAuth);
describe('create', () => {
@@ -45,6 +46,10 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext
statusCode: 200,
response: expectReservedSpecifiedResult,
},
+ solutionSpecified: {
+ statusCode: 200,
+ response: expectSolutionSpecifiedResult,
+ },
},
});
});
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts
index 0450ec2a3ff30..89e5f5a0de4a7 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts
@@ -730,9 +730,7 @@ export default function ({ getService }: FtrProviderContext) {
},
})
);
- await reportingAPI.waitForJobToFinish(res.path, undefined, undefined, {
- timeout: 80 * 1000,
- });
+ await reportingAPI.waitForJobToFinish(res.path);
const csvFile = await reportingAPI.getCompletedJobOutput(res.path);
expect((csvFile as string).length).to.be(4826973);
expectSnapshot(createPartialCsv(csvFile)).toMatch();
diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/synthetics/synthetics_enablement.ts b/x-pack/test_serverless/api_integration/test_suites/observability/synthetics/synthetics_enablement.ts
index 9925c00fe5748..e19bc8e227969 100644
--- a/x-pack/test_serverless/api_integration/test_suites/observability/synthetics/synthetics_enablement.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/observability/synthetics/synthetics_enablement.ts
@@ -21,6 +21,7 @@ const ALL_ENABLED = {
canEnable: true,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: false,
};
export default function ({ getService }: FtrProviderContext) {
@@ -42,6 +43,8 @@ export default function ({ getService }: FtrProviderContext) {
};
describe('SyntheticsEnablement', function () {
+ // fails on MKI, see https://github.com/elastic/kibana/issues/184273
+ this.tags(['failsOnMKI']);
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
const supertestWithoutAuth = getService('supertestWithoutAuth');
@@ -99,6 +102,7 @@ export default function ({ getService }: FtrProviderContext) {
isValidApiKey: false,
// api key is not there, as it's deleted at the start of the tests
isEnabled: false,
+ isServiceAllowed: false,
});
}
});
@@ -188,6 +192,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: false,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: false,
});
});
@@ -201,6 +206,7 @@ export default function ({ getService }: FtrProviderContext) {
canEnable: false,
isEnabled: true,
isValidApiKey: true,
+ isServiceAllowed: false,
});
});
});
diff --git a/x-pack/test_serverless/shared/config.base.ts b/x-pack/test_serverless/shared/config.base.ts
index 9c2454d4e7a39..0d0b69843820d 100644
--- a/x-pack/test_serverless/shared/config.base.ts
+++ b/x-pack/test_serverless/shared/config.base.ts
@@ -159,7 +159,7 @@ export default async () => {
try: 120 * 1000,
waitFor: 20 * 1000,
esRequestTimeout: 30 * 1000,
- kibanaReportCompletion: 60 * 1000,
+ kibanaReportCompletion: 80 * 1000,
kibanaStabilize: 15 * 1000,
navigateStatusPageCheck: 250,
waitForExists: 2500,