diff --git a/.buildkite/scripts/steps/artifacts/docker_image.sh b/.buildkite/scripts/steps/artifacts/docker_image.sh index caa2f02908e031..3d75d301ce8e7f 100755 --- a/.buildkite/scripts/steps/artifacts/docker_image.sh +++ b/.buildkite/scripts/steps/artifacts/docker_image.sh @@ -7,13 +7,14 @@ set -euo pipefail source .buildkite/scripts/steps/artifacts/env.sh GIT_ABBREV_COMMIT=${BUILDKITE_COMMIT:0:12} -KIBANA_IMAGE="docker.elastic.co/kibana-ci/kibana:git-$GIT_ABBREV_COMMIT" +KIBANA_IMAGE_INPUT="docker.elastic.co/kibana-ci/kibana-serverless:git-$GIT_ABBREV_COMMIT" +KIBANA_IMAGE_OUTPUT="docker.elastic.co/kibana-ci/kibana:git-$GIT_ABBREV_COMMIT" echo "--- Verify manifest does not already exist" echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co trap 'docker logout docker.elastic.co' EXIT -if docker manifest inspect $KIBANA_IMAGE &> /dev/null; then +if docker manifest inspect $KIBANA_IMAGE_OUTPUT &> /dev/null; then echo "Manifest already exists, exiting" exit 1 fi @@ -32,37 +33,36 @@ node scripts/build \ --skip-docker-contexts echo "--- Tag images" -docker rmi "$KIBANA_IMAGE" +docker rmi "$KIBANA_IMAGE_INPUT" docker load < "target/kibana-serverless-$BASE_VERSION-docker-image.tar.gz" -docker tag "$KIBANA_IMAGE" "$KIBANA_IMAGE-amd64" +docker tag "$KIBANA_IMAGE_INPUT" "$KIBANA_IMAGE_OUTPUT-amd64" -docker rmi "$KIBANA_IMAGE" +docker rmi "$KIBANA_IMAGE_INPUT" docker load < "target/kibana-serverless-$BASE_VERSION-docker-image-aarch64.tar.gz" -docker tag "$KIBANA_IMAGE" "$KIBANA_IMAGE-arm64" +docker tag "$KIBANA_IMAGE_INPUT" "$KIBANA_IMAGE_OUTPUT-arm64" echo "--- Push images" -docker image push "$KIBANA_IMAGE-arm64" -docker image push "$KIBANA_IMAGE-amd64" +docker image push "$KIBANA_IMAGE_OUTPUT-arm64" +docker image push "$KIBANA_IMAGE_OUTPUT-amd64" echo "--- Create manifest" -docker rmi "$KIBANA_IMAGE" docker manifest create \ - "$KIBANA_IMAGE" \ - --amend "$KIBANA_IMAGE-arm64" \ - --amend "$KIBANA_IMAGE-amd64" + "$KIBANA_IMAGE_OUTPUT" \ + --amend "$KIBANA_IMAGE_OUTPUT-arm64" \ + --amend "$KIBANA_IMAGE_OUTPUT-amd64" echo "--- Push manifest" -docker manifest push "$KIBANA_IMAGE" +docker manifest push "$KIBANA_IMAGE_OUTPUT" docker logout docker.elastic.co cat << EOF | buildkite-agent annotate --style "info" --context image ### Container Images - Manifest: \`$KIBANA_IMAGE\` + Manifest: \`$KIBANA_IMAGE_OUTPUT\` - AMD64: \`$KIBANA_IMAGE-amd64\` + AMD64: \`$KIBANA_IMAGE_OUTPUT-amd64\` - ARM64: \`$KIBANA_IMAGE-arm64\` + ARM64: \`$KIBANA_IMAGE_OUTPUT-arm64\` EOF echo "--- Build dependencies report" diff --git a/fleet_packages.json b/fleet_packages.json index eef982351e4551..34d807ac46b9c1 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -34,7 +34,7 @@ }, { "name": "endpoint", - "version": "8.7.1" + "version": "8.8.0" }, { "name": "fleet_server", diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 13a2209e2e47ea..8563c19f9d5386 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -81,7 +81,7 @@ export const CreateDockerUbuntu: Task = { }; export const CreateDockerServerless: Task = { - description: 'Creating Docker Ubuntu image', + description: 'Creating Docker Serverless image', async run(config, log, build) { await runDockerGenerator(config, log, build, { diff --git a/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json b/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json index af0d68d0df3eed..9f6ecfe4b8da57 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json +++ b/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json @@ -66,10 +66,10 @@ "required": ["containerImageName"] }, { - "required": ["containerImageFullName"] + "required": ["containerImageTag"] }, { - "required": ["containerImageTag"] + "required": ["containerImageFullName"] }, { "required": ["kubernetesClusterId"] @@ -100,6 +100,19 @@ "name": { "type": "string" }, + "operation": { + "type": "array", + "minItems": 1, + "items": { + "enum": [ + "createExecutable", + "modifyExecutable", + "createFile", + "modifyFile", + "deleteFile" + ] + } + }, "containerImageName": { "type": "array", "minItems": 1, @@ -159,19 +172,6 @@ "type": "string" } }, - "operation": { - "type": "array", - "minItems": 1, - "items": { - "enum": [ - "createExecutable", - "modifyExecutable", - "createFile", - "modifyFile", - "deleteFile" - ] - } - }, "targetFilePath": { "type": "array", "minItems": 1, @@ -214,10 +214,10 @@ "required": ["containerImageName"] }, { - "required": ["containerImageFullName"] + "required": ["containerImageTag"] }, { - "required": ["containerImageTag"] + "required": ["containerImageFullName"] }, { "required": ["kubernetesClusterId"] @@ -248,6 +248,13 @@ "name": { "type": "string" }, + "operation": { + "type": "array", + "minItems": 1, + "items": { + "enum": ["fork", "exec"] + } + }, "containerImageName": { "type": "array", "minItems": 1, @@ -307,13 +314,6 @@ "type": "string" } }, - "operation": { - "type": "array", - "minItems": 1, - "items": { - "enum": ["fork", "exec"] - } - }, "processExecutable": { "type": "array", "minItems": 1, diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_cvsscore_color.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_cvsscore_color.test.ts deleted file mode 100644 index 67eab9cddd7354..00000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_cvsscore_color.test.ts +++ /dev/null @@ -1,35 +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 { euiThemeVars } from '@kbn/ui-theme'; -import { getCvsScoreColor } from './get_cvsscore_color'; - -describe('getCvsScoreColor', () => { - it('returns correct color for low severity score', () => { - expect(getCvsScoreColor(1.5)).toBe(euiThemeVars.euiColorVis0); - }); - - it('returns correct color for medium severity score', () => { - expect(getCvsScoreColor(5.5)).toBe(euiThemeVars.euiColorVis7); - }); - - it('returns correct color for high severity score', () => { - expect(getCvsScoreColor(7.9)).toBe(euiThemeVars.euiColorVis9); - }); - - it('returns correct color for critical severity score', () => { - expect(getCvsScoreColor(10.0)).toBe(euiThemeVars.euiColorDanger); - }); - - it('returns error message for invalid score', () => { - expect(getCvsScoreColor(-1)).toBe(undefined); - }); - - it('returns error message for invalid score when score is 0.0', () => { - expect(getCvsScoreColor(0.0)).toBe(undefined); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_cvsscore_color.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts similarity index 54% rename from x-pack/plugins/cloud_security_posture/public/common/utils/get_cvsscore_color.ts rename to x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts index cbde6d2b885bdf..56a3d2d2e6088d 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_cvsscore_color.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts @@ -8,15 +8,28 @@ import { euiThemeVars } from '@kbn/ui-theme'; export const getCvsScoreColor = (score: number): string | undefined => { - if (score >= 0.1 && score <= 3.9) { + if (score <= 4) { return euiThemeVars.euiColorVis0; // low severity - } else if (score >= 4.0 && score <= 6.9) { + } else if (score >= 4 && score <= 7) { return euiThemeVars.euiColorVis7; // medium severity - } else if (score >= 7.0 && score <= 8.9) { + } else if (score >= 7 && score <= 9) { return euiThemeVars.euiColorVis9; // high severity - } else if (score >= 9.0 && score <= 10.0) { + } else if (score >= 9) { return euiThemeVars.euiColorDanger; // critical severity - } else { - return undefined; // if the score is not within the valid range + } +}; + +export const getSeverityStatusColor = (severity: string): string | undefined => { + switch (severity) { + case 'LOW': + return euiThemeVars.euiColorVis0; + case 'MEDIUM': + return euiThemeVars.euiColorVis7; + case 'HIGH': + return euiThemeVars.euiColorVis9; + case 'CRITICAL': + return euiThemeVars.euiColorDanger; + default: + return '#aaa'; } }; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts new file mode 100644 index 00000000000000..e96e6f8149f114 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts @@ -0,0 +1,53 @@ +/* + * 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 { euiThemeVars } from '@kbn/ui-theme'; +import { getCvsScoreColor, getSeverityStatusColor } from './get_vulnerability_colors'; + +describe('getCvsScoreColor', () => { + it('returns correct color for low severity score', () => { + expect(getCvsScoreColor(1.5)).toBe(euiThemeVars.euiColorVis0); + }); + + it('returns correct color for medium severity score', () => { + expect(getCvsScoreColor(5.5)).toBe(euiThemeVars.euiColorVis7); + }); + + it('returns correct color for high severity score', () => { + expect(getCvsScoreColor(7.9)).toBe(euiThemeVars.euiColorVis9); + }); + + it('returns correct color for critical severity score', () => { + expect(getCvsScoreColor(10.0)).toBe(euiThemeVars.euiColorDanger); + }); + + it('returns correct color for low severity score for undefined value', () => { + expect(getCvsScoreColor(-0.2)).toBe(euiThemeVars.euiColorVis0); + }); +}); + +describe('getSeverityStatusColor', () => { + it('should return the correct color for LOW severity', () => { + expect(getSeverityStatusColor('LOW')).toBe(euiThemeVars.euiColorVis0); + }); + + it('should return the correct color for MEDIUM severity', () => { + expect(getSeverityStatusColor('MEDIUM')).toBe(euiThemeVars.euiColorVis7); + }); + + it('should return the correct color for HIGH severity', () => { + expect(getSeverityStatusColor('HIGH')).toBe(euiThemeVars.euiColorVis9); + }); + + it('should return the correct color for CRITICAL severity', () => { + expect(getSeverityStatusColor('CRITICAL')).toBe(euiThemeVars.euiColorDanger); + }); + + it('should return #aaa for an unknown severity', () => { + expect(getSeverityStatusColor('UNKNOWN')).toBe('#aaa'); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx index 4275ed433d85e7..bf776f27c04a15 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx @@ -9,7 +9,7 @@ import { EuiBadge, EuiIcon, EuiTextColor } from '@elastic/eui'; import React from 'react'; import { css } from '@emotion/react'; import { float } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { getCvsScoreColor } from '../common/utils/get_cvsscore_color'; +import { getCvsScoreColor, getSeverityStatusColor } from '../common/utils/get_vulnerability_colors'; interface CVSScoreBadgeProps { score: float; @@ -18,7 +18,6 @@ interface CVSScoreBadgeProps { interface SeverityStatusBadgeProps { status: string; - score?: float; } export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => { @@ -54,8 +53,8 @@ export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => { ); }; -export const SeverityStatusBadge = ({ score, status }: SeverityStatusBadgeProps) => { - const color = score ? getCvsScoreColor(score) : undefined; +export const SeverityStatusBadge = ({ status }: SeverityStatusBadgeProps) => { + const color = getSeverityStatusColor(status); return (
{status} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx index 38b9dfcacf9d0f..f048347d686b5c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx @@ -53,7 +53,7 @@ export const Vulnerabilities = () => { const { data, isLoading, error } = useFilteredDataView(LATEST_VULNERABILITIES_INDEX_PATTERN); const getSetupStatus = useCspSetupStatusApi(); - if (getSetupStatus?.data?.vuln_mgmt.status !== 'indexed') return ; + if (getSetupStatus?.data?.vuln_mgmt?.status !== 'indexed') return ; if (error) { return ; @@ -247,12 +247,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { if (!vulnerabilityRow.vulnerability.severity) { return null; } - return ( - - ); + return ; } if (columnId === vulnerabilitiesColumns.package_version) { return ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx index 5ea65846950bf3..195c74c8032419 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.tsx @@ -27,10 +27,7 @@ import { VulnerabilityOverviewTab } from './vulnerability_overview_tab'; import { VulnerabilityJsonTab } from './vulnerability_json_tab'; import { SeverityStatusBadge } from '../../../components/vulnerability_badges'; import { VulnerabilityRecord } from '../types'; -import { - SEVERITY_STATUS_VULNERABILITY_FLYOUT, - TAB_ID_VULNERABILITY_FLYOUT, -} from '../test_subjects'; +import { TAB_ID_VULNERABILITY_FLYOUT } from '../test_subjects'; const overviewTabId = 'overview'; const jsonTabId = 'json'; @@ -110,11 +107,8 @@ export const VulnerabilityFindingFlyout = ({ gap: ${euiThemeVars.euiSizeS}; `} > - - + + ({ })), })); -const mockNotableAnomaliesSearch = jest.fn().mockResolvedValue({}); +const mockAnomaliesSearch = jest.fn().mockResolvedValue({}); jest.mock('../api/anomalies_search', () => ({ - notableAnomaliesSearch: jest - .fn() - .mockImplementation((params: unknown) => mockNotableAnomaliesSearch(params)), + anomaliesSearch: jest.fn().mockImplementation((params: unknown) => mockAnomaliesSearch(params)), })); -describe('useNotableAnomaliesSearch', () => { +describe('useAggregatedAnomaliesByJob', () => { beforeEach(() => { jest.clearAllMocks(); }); it('returns the initial value', () => { - const { result } = renderHook(() => useNotableAnomaliesSearch({ skip: true, from, to }), { + const { result } = renderHook(() => useAggregatedAnomaliesByJob({ skip: true, from, to }), { wrapper: TestProviders, }); expect(result.current.data.length).toEqual(0); }); - it('calls notableAnomaliesSearch when skip is false', async () => { + it('calls anomaliesSearch when skip is false', async () => { await act(async () => { - renderHook(() => useNotableAnomaliesSearch({ skip: false, from, to }), { + renderHook(() => useAggregatedAnomaliesByJob({ skip: false, from, to }), { wrapper: TestProviders, }); }); - expect(mockNotableAnomaliesSearch).toHaveBeenCalled(); + expect(mockAnomaliesSearch).toHaveBeenCalled(); }); - it('does NOT call notableAnomaliesSearch when skip is true', async () => { + it('does NOT call anomaliesSearch when skip is true', async () => { await act(async () => { - renderHook(() => useNotableAnomaliesSearch({ skip: true, from, to }), { + renderHook(() => useAggregatedAnomaliesByJob({ skip: true, from, to }), { wrapper: TestProviders, }); }); - expect(mockNotableAnomaliesSearch).not.toHaveBeenCalled(); + expect(mockAnomaliesSearch).not.toHaveBeenCalled(); }); it('refetch calls useSecurityJobs().refetch', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( - () => useNotableAnomaliesSearch({ skip: false, from, to }), + () => useAggregatedAnomaliesByJob({ skip: false, from, to }), { wrapper: TestProviders, } @@ -91,11 +89,11 @@ describe('useNotableAnomaliesSearch', () => { it('returns formated data', async () => { await act(async () => { const jobCount = { key: jobId, doc_count: 99 }; - mockNotableAnomaliesSearch.mockResolvedValue({ + mockAnomaliesSearch.mockResolvedValue({ aggregations: { number_of_anomalies: { buckets: [jobCount] } }, }); const { result, waitForNextUpdate } = renderHook( - () => useNotableAnomaliesSearch({ skip: false, from, to }), + () => useAggregatedAnomaliesByJob({ skip: false, from, to }), { wrapper: TestProviders, } @@ -141,7 +139,7 @@ describe('useNotableAnomaliesSearch', () => { }, }; - mockNotableAnomaliesSearch.mockResolvedValue({ + mockAnomaliesSearch.mockResolvedValue({ aggregations: { number_of_anomalies: { buckets: [fistJobCount, secondJobCount] } }, }); @@ -153,7 +151,7 @@ describe('useNotableAnomaliesSearch', () => { }); const { result, waitForNextUpdate } = renderHook( - () => useNotableAnomaliesSearch({ skip: false, from, to }), + () => useAggregatedAnomaliesByJob({ skip: false, from, to }), { wrapper: TestProviders, } @@ -170,9 +168,9 @@ describe('useNotableAnomaliesSearch', () => { it('does not throw error when aggregations is undefined', async () => { await act(async () => { - mockNotableAnomaliesSearch.mockResolvedValue({}); + mockAnomaliesSearch.mockResolvedValue({}); const { waitForNextUpdate } = renderHook( - () => useNotableAnomaliesSearch({ skip: false, from, to }), + () => useAggregatedAnomaliesByJob({ skip: false, from, to }), { wrapper: TestProviders, } diff --git a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts index baa4ac03623e14..9a5314d0a0d0bf 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts @@ -12,9 +12,7 @@ import { DEFAULT_ANOMALY_SCORE } from '../../../../../common/constants'; import * as i18n from './translations'; import { useUiSetting$ } from '../../../lib/kibana'; import { useAppToasts } from '../../../hooks/use_app_toasts'; -import { notableAnomaliesSearch } from '../api/anomalies_search'; -import type { NotableAnomaliesJobId } from '../../../../overview/components/entity_analytics/anomalies/config'; -import { NOTABLE_ANOMALIES_IDS } from '../../../../overview/components/entity_analytics/anomalies/config'; +import { anomaliesSearch } from '../api/anomalies_search'; import { getAggregatedAnomaliesQuery } from '../../../../overview/components/entity_analytics/anomalies/query'; import type { inputsModel } from '../../../store'; import { useSecurityJobs } from '../../ml_popover/hooks/use_security_jobs'; @@ -26,23 +24,23 @@ export enum AnomalyEntity { } export interface AnomaliesCount { - name: NotableAnomaliesJobId | string; + name: string; count: number; entity: AnomalyEntity; job?: SecurityJob; } -interface UseNotableAnomaliesSearchProps { +interface UseAggregatedAnomaliesByJobProps { skip: boolean; from: string; to: string; } -export const useNotableAnomaliesSearch = ({ +export const useAggregatedAnomaliesByJob = ({ skip, from, to, -}: UseNotableAnomaliesSearchProps): { +}: UseAggregatedAnomaliesByJobProps): { isLoading: boolean; data: AnomaliesCount[]; refetch: inputsModel.Refetch; @@ -60,23 +58,17 @@ export const useNotableAnomaliesSearch = ({ const { addError } = useAppToasts(); const [anomalyScoreThreshold] = useUiSetting$(DEFAULT_ANOMALY_SCORE); - const { notableAnomaliesJobs, query } = useMemo(() => { - const newNotableAnomaliesJobs = securityJobs.filter(({ id }) => - NOTABLE_ANOMALIES_IDS.some((notableJobId) => id === notableJobId) - ); - - const newQuery = getAggregatedAnomaliesQuery({ - jobIds: newNotableAnomaliesJobs.map(({ id }) => id), - anomalyScoreThreshold, - from, - to, - }); - - return { - query: newQuery, - notableAnomaliesJobs: newNotableAnomaliesJobs, - }; - }, [securityJobs, anomalyScoreThreshold, from, to]); + const { query } = useMemo( + () => ({ + query: getAggregatedAnomaliesQuery({ + jobIds: securityJobs.map(({ id }) => id), + anomalyScoreThreshold, + from, + to, + }), + }), + [securityJobs, anomalyScoreThreshold, from, to] + ); useEffect(() => { let isSubscribed = true; @@ -85,16 +77,16 @@ export const useNotableAnomaliesSearch = ({ async function fetchAnomaliesSearch() { if (!isSubscribed) return; - if (skip || !isMlUser || notableAnomaliesJobs.length === 0) { + if (skip || !isMlUser || securityJobs.length === 0) { setLoading(false); return; } setLoading(true); try { - const response = await notableAnomaliesSearch( + const response = await anomaliesSearch( { - jobIds: notableAnomaliesJobs.filter((job) => job.isInstalled).map(({ id }) => id), + jobIds: securityJobs.filter((job) => job.isInstalled).map(({ id }) => id), query, }, abortCtrl.signal @@ -103,7 +95,7 @@ export const useNotableAnomaliesSearch = ({ if (isSubscribed) { setLoading(false); const buckets = response.aggregations?.number_of_anomalies.buckets ?? []; - setData(formatResultData(buckets, notableAnomaliesJobs)); + setData(formatResultData(buckets, securityJobs)); } } catch (error) { if (isSubscribed && error.name !== 'AbortError') { @@ -119,7 +111,7 @@ export const useNotableAnomaliesSearch = ({ isSubscribed = false; abortCtrl.abort(); }; - }, [skip, isMlUser, addError, query, notableAnomaliesJobs, refetchJobs]); + }, [skip, isMlUser, addError, query, securityJobs, refetchJobs]); return { isLoading: loading || jobsLoading, data, refetch: refetchJobs }; }; @@ -129,16 +121,14 @@ function formatResultData( key: string; doc_count: number; }>, - notableAnomaliesJobs: SecurityJob[] + anomaliesJobs: SecurityJob[] ): AnomaliesCount[] { - const unsortedAnomalies: AnomaliesCount[] = NOTABLE_ANOMALIES_IDS.map((notableJobId) => { - const job = notableAnomaliesJobs.find(({ id }) => id === notableJobId); - + const unsortedAnomalies: AnomaliesCount[] = anomaliesJobs.map((job) => { const bucket = buckets.find(({ key }) => key === job?.id); const hasUserName = has("entity.hits.hits[0]._source['user.name']", bucket); return { - name: job?.customSettings?.security_app_display_name ?? notableJobId, + name: job?.customSettings?.security_app_display_name ?? job.id, count: bucket?.doc_count ?? 0, entity: hasUserName ? AnomalyEntity.User : AnomalyEntity.Host, job, diff --git a/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts b/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts index 966137c616f99c..d7cfa924b0de07 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { NotableAnomaliesJobId } from '../../../../overview/components/entity_analytics/anomalies/config'; import { KibanaServices } from '../../../lib/kibana'; export interface Body { @@ -17,14 +16,14 @@ export interface AnomaliesSearchResponse { aggregations?: { number_of_anomalies: { buckets: Array<{ - key: NotableAnomaliesJobId; + key: string; doc_count: number; }>; }; }; } -export const notableAnomaliesSearch = async ( +export const anomaliesSearch = async ( body: Body, signal: AbortSignal ): Promise => { diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts deleted file mode 100644 index 1396568384b171..00000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts +++ /dev/null @@ -1,53 +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. - */ - -export const NOTABLE_ANOMALIES_IDS = [ - 'auth_rare_source_ip_for_a_user', - 'packetbeat_dns_tunneling', - 'packetbeat_rare_server_domain', - 'packetbeat_rare_dns_question', - 'suspicious_login_activity', - 'v3_windows_anomalous_script', - 'high_count_network_denies', - 'v3_windows_anomalous_process_all_hosts', - 'v3_linux_rare_metadata_process', - 'packetbeat_rare_user_agent', - 'v3_linux_anomalous_process_all_hosts', - 'packetbeat_rare_urls', - 'v3_windows_anomalous_path_activity', - 'v3_windows_anomalous_process_creation', - 'v3_linux_system_process_discovery', - 'v3_linux_system_user_discovery', - 'high_count_by_destination_country', - 'auth_high_count_logon_events', - 'v3_linux_anomalous_user_name', - 'v3_rare_process_by_host_windows', - 'v3_linux_anomalous_network_activity', - 'auth_high_count_logon_fails', - 'auth_high_count_logon_events_for_a_source_ip', - 'v3_linux_rare_metadata_user', - 'rare_destination_country', - 'v3_linux_system_information_discovery', - 'v3_linux_rare_user_compiler', - 'v3_windows_anomalous_user_name', - 'v3_rare_process_by_host_linux', - 'v3_windows_anomalous_network_activity', - 'auth_rare_hour_for_a_user', - 'v3_windows_rare_metadata_user', - 'v3_windows_rare_user_type10_remote_login', - 'v3_linux_anomalous_network_port_activity', - 'v3_linux_rare_sudo_user', - 'v3_windows_anomalous_service', - 'v3_windows_rare_metadata_process', - 'v3_windows_rare_user_runas_event', - 'v3_linux_network_connection_discovery', - 'v3_linux_network_configuration_discovery', - 'auth_rare_user', - 'high_count_network_events', -] as const; - -export type NotableAnomaliesJobId = typeof NOTABLE_ANOMALIES_IDS[number]; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx index 58f075ff76b21d..3e6b85d7beca9f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx @@ -30,7 +30,7 @@ jest.mock('../../../../common/utils/route/use_route_spy', () => ({ ]), })); -const mockUseNotableAnomaliesSearch = jest.fn().mockReturnValue({ +const mockUseAggregatedAnomaliesByJob = jest.fn().mockReturnValue({ isLoading: false, data: [], refetch: jest.fn(), @@ -51,7 +51,7 @@ jest.mock('../../../../common/components/ml/anomaly/use_anomalies_search', () => ); return { ...original, - useNotableAnomaliesSearch: () => mockUseNotableAnomaliesSearch(), + useAggregatedAnomaliesByJob: () => mockUseAggregatedAnomaliesByJob(), }; }); @@ -95,7 +95,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), @@ -126,7 +126,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), @@ -152,7 +152,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), @@ -178,7 +178,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), @@ -208,7 +208,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), @@ -232,7 +232,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: true, data: [jobCount], refetch: jest.fn(), @@ -260,7 +260,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), @@ -288,7 +288,7 @@ describe('EntityAnalyticsAnomalies', () => { entity: AnomalyEntity.User, }; - mockUseNotableAnomaliesSearch.mockReturnValue({ + mockUseAggregatedAnomaliesByJob.mockReturnValue({ isLoading: false, data: [jobCount], refetch: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx index 44213690721ea3..e72369c97379d9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx @@ -20,7 +20,7 @@ import { HeaderSection } from '../../../../common/components/header_section'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; import * as i18n from './translations'; -import { useNotableAnomaliesSearch } from '../../../../common/components/ml/anomaly/use_anomalies_search'; +import { useAggregatedAnomaliesByJob } from '../../../../common/components/ml/anomaly/use_anomalies_search'; import { useAnomaliesColumns } from './columns'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; @@ -66,7 +66,7 @@ export const EntityAnalyticsAnomalies = () => { isLoading: isSearchLoading, data, refetch, - } = useNotableAnomaliesSearch({ + } = useAggregatedAnomaliesByJob({ skip: !toggleStatus, from, to, diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts index d8dbcd8664c959..733a0d8728f3c7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export const ANOMALIES_TITLE = i18n.translate( 'xpack.securitySolution.entityAnalytics.anomalies.anomaliesTitle', { - defaultMessage: 'Notable Anomalies', + defaultMessage: 'Anomalies', } ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx index 85cc8c00438970..46d37f15a6f689 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx @@ -25,7 +25,7 @@ import { hostsActions } from '../../../../explore/hosts/store'; import { usersActions } from '../../../../explore/users/store'; import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirect_to_users'; import { UsersTableType } from '../../../../explore/users/store/model'; -import { useNotableAnomaliesSearch } from '../../../../common/components/ml/anomaly/use_anomalies_search'; +import { useAggregatedAnomaliesByJob } from '../../../../common/components/ml/anomaly/use_anomalies_search'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; @@ -70,7 +70,7 @@ export const EntityAnalyticsHeader = () => { riskEntity: RiskScoreEntity.user, }); - const { data } = useNotableAnomaliesSearch({ skip: false, from, to }); + const { data } = useAggregatedAnomaliesByJob({ skip: false, from, to }); const dispatch = useDispatch(); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); diff --git a/x-pack/plugins/security_solution/public/overview/links.ts b/x-pack/plugins/security_solution/public/overview/links.ts index 07cc2a491cf025..020d257c02cbef 100644 --- a/x-pack/plugins/security_solution/public/overview/links.ts +++ b/x-pack/plugins/security_solution/public/overview/links.ts @@ -82,7 +82,7 @@ export const entityAnalyticsLinks: LinkItem = { landingImage: entityAnalyticsDashboard, description: i18n.translate('xpack.securitySolution.appLinks.entityAnalyticsDescription', { defaultMessage: - 'Entity analytics, notable anomalies, and threats to narrow down the monitoring surface area.', + 'Entity analytics, anomalies, and threats to narrow down the monitoring surface area.', }), path: ENTITY_ANALYTICS_PATH, capabilities: [`${SERVER_APP_ID}.show`], diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts index 0a20795950d856..078dfa7b45d049 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_params.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; -export const SyntheticsParamCode = t.intersection([ +export const SyntheticsParamSOCodec = t.intersection([ t.interface({ key: t.string, value: t.string, @@ -19,4 +19,18 @@ export const SyntheticsParamCode = t.intersection([ }), ]); -export type SyntheticsParam = t.TypeOf; +export type SyntheticsParamSO = t.TypeOf; + +export const SyntheticsParamRequestCodec = t.intersection([ + t.interface({ + key: t.string, + value: t.string, + }), + t.partial({ + description: t.string, + tags: t.array(t.string), + share_across_spaces: t.boolean, + }), +]); + +export type SyntheticsParamRequest = t.TypeOf; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx index 7f89c6dc0d3c03..64f19ad901003d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx @@ -5,6 +5,7 @@ * 2.0. */ import React, { useCallback, useEffect, useState } from 'react'; +import { ALL_SPACES_ID } from '@kbn/security-plugin/public'; import { EuiFlyout, EuiFlyoutBody, @@ -25,7 +26,7 @@ import { useDispatch } from 'react-redux'; import { apiService } from '../../../../../utils/api_service'; import { ClientPluginsStart } from '../../../../../plugin'; import { ListParamItem } from './params_list'; -import { SyntheticsParam } from '../../../../../../common/runtime_types'; +import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; import { useFormWrapped } from '../../../../../hooks/use_form_wrapped'; import { AddParamForm } from './add_param_form'; import { SYNTHETICS_API_URLS } from '../../../../../../common/constants'; @@ -46,7 +47,7 @@ export const AddParamFlyout = ({ const { id, ...dataToSave } = isEditingItem ?? {}; - const form = useFormWrapped({ + const form = useFormWrapped({ mode: 'onSubmit', reValidateMode: 'onChange', shouldFocusError: true, @@ -66,7 +67,7 @@ export const AddParamFlyout = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [setIsEditingItem]); - const [paramData, setParamData] = useState(null); + const [paramData, setParamData] = useState(null); const { application } = useKibana().services; @@ -74,15 +75,24 @@ export const AddParamFlyout = ({ if (!paramData) { return; } + const { namespaces, ...paramRequest } = paramData; + const shareAcrossSpaces = namespaces?.includes(ALL_SPACES_ID); if (isEditingItem) { - return apiService.put(SYNTHETICS_API_URLS.PARAMS, { id, ...paramData }); + return apiService.put(SYNTHETICS_API_URLS.PARAMS, { + id, + ...paramRequest, + share_across_spaces: shareAcrossSpaces, + }); } - return apiService.post(SYNTHETICS_API_URLS.PARAMS, paramData); + return apiService.post(SYNTHETICS_API_URLS.PARAMS, { + ...paramRequest, + share_across_spaces: shareAcrossSpaces, + }); }, [paramData]); const canSave = (application?.capabilities.uptime.save ?? false) as boolean; - const onSubmit = (formData: SyntheticsParam) => { + const onSubmit = (formData: SyntheticsParamSO) => { setParamData(formData); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx index af079c474afec0..240314dc3b06fe 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_form.tsx @@ -5,6 +5,7 @@ * 2.0. */ import React from 'react'; +import { ALL_SPACES_ID } from '@kbn/security-plugin/public'; import { EuiCheckbox, EuiComboBox, @@ -15,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Controller, useFormContext, useFormState } from 'react-hook-form'; -import { SyntheticsParam } from '../../../../../../common/runtime_types'; +import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; import { ListParamItem } from './params_list'; export const AddParamForm = ({ @@ -25,8 +26,8 @@ export const AddParamForm = ({ items: ListParamItem[]; isEditingItem: ListParamItem | null; }) => { - const { register, control } = useFormContext(); - const { errors } = useFormState(); + const { register, control } = useFormContext(); + const { errors } = useFormState(); const tagsList = items.reduce((acc, item) => { const tags = item.tags || []; @@ -119,7 +120,7 @@ export const AddParamForm = ({ aria-label={NAMESPACES_LABEL} onChange={(e) => { if (e.target.checked) { - field.onChange(['*']); + field.onChange([ALL_SPACES_ID]); } else { field.onChange([]); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx index e8f43c081d2da6..8f06492eb879ba 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/params_list.tsx @@ -24,12 +24,12 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { useDebounce } from 'react-use'; import { TableTitle } from '../../common/components/table_title'; import { ParamsText } from './params_text'; -import { SyntheticsParam } from '../../../../../../common/runtime_types'; +import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; import { useParamsList } from '../hooks/use_params_list'; import { AddParamFlyout } from './add_param_flyout'; import { DeleteParam } from './delete_param'; -export interface ListParamItem extends SyntheticsParam { +export interface ListParamItem extends SyntheticsParamSO { id: string; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts index 9b566f61074387..ad95851ea68c0b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_params_list.ts @@ -8,13 +8,13 @@ import { useFetcher } from '@kbn/observability-plugin/public'; import { SavedObject } from '@kbn/core-saved-objects-common'; import { useMemo } from 'react'; -import { SyntheticsParam } from '../../../../../../common/runtime_types'; +import { SyntheticsParamSO } from '../../../../../../common/runtime_types'; import { apiService } from '../../../../../utils/api_service'; import { SYNTHETICS_API_URLS } from '../../../../../../common/constants'; export const useParamsList = (lastRefresh: number) => { const { data, loading } = useFetcher< - Promise<{ data: Array> }> + Promise<{ data: Array> }> >(() => { return apiService.get(SYNTHETICS_API_URLS.PARAMS); }, [lastRefresh]); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/hooks/use_run_once_errors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/hooks/use_run_once_errors.ts index 6e0d8c0faf0efd..9859b5b0216bc9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/hooks/use_run_once_errors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/hooks/use_run_once_errors.ts @@ -15,7 +15,9 @@ export function useRunOnceErrors({ serviceError, errors, locations, + showErrors = true, }: { + showErrors?: boolean; testRunId: string; serviceError?: Error; errors: ServiceLocationErrors; @@ -55,40 +57,59 @@ export function useRunOnceErrors({ const expectPings = publicLocations.length - (locationErrors ?? []).filter(({ locationId }) => !!locationId).length; + const locationErrorReasons = useMemo(() => { + return (locationErrors ?? []) + .map(({ error }) => error?.reason) + .filter((reason) => !!reason) + .filter((reason, i, arr) => arr.indexOf(reason) === i); + }, [locationErrors]); const hasBlockingError = !!runOnceServiceError || (locationErrors?.length && locationErrors?.length === publicLocations.length); const errorMessages = useMemo(() => { if (hasBlockingError) { - return [{ name: 'Error', message: PushErrorService, title: PushErrorLabel }]; + return locationErrorReasons.length === 1 + ? [ + { + name: 'Error', + message: locationErrorReasons[0] ?? PushErrorService, + title: RunErrorLabel, + }, + ] + : [{ name: 'Error', message: PushErrorService, title: PushErrorLabel }]; } else if (locationErrors?.length > 0) { // If only some of the locations were unsuccessful return locationErrors - .map(({ locationId }) => locationsById[locationId]) - .filter((location) => !!location) - .map((location) => ({ + .map(({ locationId, error }) => ({ location: locationsById[locationId], error })) + .filter((locationWithError) => !!locationWithError.location) + .map(({ location, error }) => ({ name: 'Error', - message: getLocationTestErrorLabel(location.label), + message: getLocationTestErrorLabel(location.label, error?.reason ?? ''), title: RunErrorLabel, })); } return []; - }, [locationsById, locationErrors, hasBlockingError]); + }, [locationsById, locationErrors, locationErrorReasons, hasBlockingError]); useEffect(() => { - errorMessages.forEach( - ({ name, message, title }: { name: string; message: string; title: string }) => { - kibanaService.toasts.addError({ name, message }, { title }); - } - ); - }, [errorMessages]); + if (showErrors) { + errorMessages.forEach( + ({ name, message, title }: { name: string; message: string; title: string }) => { + kibanaService.toasts.addError({ name, message }, { title }); + } + ); + } + }, [errorMessages, showErrors]); return { expectPings, hasBlockingError, - blockingErrorMessage: hasBlockingError ? PushErrorService : null, + blockingErrorTitle: hasBlockingError ? PushErrorLabel : null, + blockingErrorMessage: hasBlockingError + ? `${PushErrorService} ${errorMessages[0]?.message}` + : null, errorMessages, }; } @@ -101,10 +122,10 @@ const RunErrorLabel = i18n.translate('xpack.synthetics.testRun.runErrorLabel', { defaultMessage: 'Error running test', }); -const getLocationTestErrorLabel = (locationName: string) => - i18n.translate('xpack.synthetics.testRun.runErrorLocation', { - defaultMessage: 'Failed to run monitor on location {locationName}.', - values: { locationName }, +const getLocationTestErrorLabel = (locationName: string, reason: string) => + i18n.translate('xpack.synthetics.testRun.runErrorLocation.reason', { + defaultMessage: 'Failed to run monitor on location {locationName}. {reason}', + values: { locationName, reason }, }); const PushErrorService = i18n.translate('xpack.synthetics.testRun.pushError', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/manual_test_run_mode.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/manual_test_run_mode.tsx index 4ed8aed3974d5c..1ab96ee2de55f5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/manual_test_run_mode.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/manual_test_run_mode.tsx @@ -15,11 +15,14 @@ import { Locations } from '../../../../../../common/runtime_types'; export function ManualTestRunMode({ manualTestRun, onDone, + showErrors, }: { + showErrors: boolean; manualTestRun: ManualTestRun; onDone: (testRunId: string) => void; }) { const { expectPings } = useRunOnceErrors({ + showErrors, testRunId: manualTestRun.testRunId!, locations: (manualTestRun.monitor!.locations ?? []) as Locations, errors: manualTestRun.errors ?? [], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode.tsx index f3c37e71fa5654..842f82b3821293 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode.tsx @@ -26,12 +26,13 @@ export function TestNowMode({ testRun: TestRun; onDone: (testRunId: string) => void; }) { - const { hasBlockingError, blockingErrorMessage, expectPings } = useRunOnceErrors({ - testRunId: testRun.id, - serviceError, - errors: errors ?? [], - locations: (testRun.monitor.locations ?? []) as Locations, - }); + const { hasBlockingError, blockingErrorTitle, blockingErrorMessage, expectPings } = + useRunOnceErrors({ + testRunId: testRun.id, + serviceError, + errors: errors ?? [], + locations: (testRun.monitor.locations ?? []) as Locations, + }); useEffect(() => { if (!isPushing && (!testRun.id || hasBlockingError)) { @@ -46,7 +47,9 @@ export function TestNowMode({ return ( {(hasBlockingError && !isPushing && ( - + + {blockingErrorMessage} + )) || null} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout_container.tsx index a0b7e4c6b30472..ddf21c869c1a29 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout_container.tsx @@ -17,13 +17,6 @@ import { testNowRunsSelector, TestRunStatus, } from '../../state/manual_test_runs'; -import { MonitorFields } from '../../../../../common/runtime_types'; - -export interface TestRun { - id: string; - monitor: MonitorFields; - runOnceMode: boolean; -} export function TestNowModeFlyoutContainer() { const dispatch = useDispatch(); @@ -82,12 +75,15 @@ export function TestNowModeFlyoutContainer() { return ( <> {Object.values(testNowRuns) - .filter((val) => val.testRunId) + .filter( + (val) => val.testRunId && (val.status === 'in-progress' || val.status === 'loading') + ) .map((manualTestRun) => ( ))} {flyout} diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project_legacy.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project_legacy.ts index cfb1ca2f680e37..41d2b5f15ad800 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project_legacy.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project_legacy.ts @@ -5,14 +5,12 @@ * 2.0. */ import { schema } from '@kbn/config-schema'; -import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { i18n } from '@kbn/i18n'; import { UMServerLibs } from '../../legacy_uptime/lib/lib'; import { ProjectMonitor } from '../../../common/runtime_types'; import { SyntheticsStreamingRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { getAllLocations } from '../../synthetics_service/get_all_locations'; -import { ProjectMonitorFormatterLegacy } from '../../synthetics_service/project_monitor/project_monitor_formatter_legacy'; const MAX_PAYLOAD_SIZE = 1048576 * 20; // 20MiB @@ -33,63 +31,34 @@ export const addSyntheticsProjectMonitorRouteLegacy: SyntheticsStreamingRouteFac maxBytes: MAX_PAYLOAD_SIZE, }, }, - handler: async ({ - request, - savedObjectsClient, - server, - syntheticsMonitorClient, - subject, - }): Promise => { + handler: async ({ server, subject, request }): Promise => { const monitors = (request.body?.monitors as ProjectMonitor[]) || []; - - try { - const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { - id: DEFAULT_SPACE_ID, - }; - - const { keep_stale: keepStale, project: projectId } = request.body || {}; - const { publicLocations, privateLocations } = await getAllLocations({ - server, - syntheticsMonitorClient, - savedObjectsClient, - }); - const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient(); - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId, - spaceId, - keepStale, - locations: publicLocations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient, - monitors, - server, - syntheticsMonitorClient, - request, - subject, - }); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - subject?.next({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }); - } catch (error) { - if (error?.output?.statusCode === 404) { - const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; - subject?.next(`Unable to create monitors. Kibana space '${spaceId}' does not exist.`); - subject?.next({ failedMonitors: monitors.map((m) => m.id) }); - } else { - subject?.error(error); + const failureMessage = i18n.translate('xpack.synthetics.server.projectMonitors.legacy.error', { + defaultMessage: `Failed to create monitor`, + }); + const deprecationNotice = i18n.translate( + 'xpack.synthetics.server.projectMonitors.legacy.deprecation', + { + defaultMessage: `This version of @elastic/synthetics is not supported in Kibana {version}. Please upgrade to ^1.0.0.`, + values: { + version: server.stackVersion, + }, } - } finally { - subject?.complete(); - } + ); + + subject?.next(deprecationNotice); + subject?.next({ + failedMonitors: monitors.map((m) => ({ + id: m.id, + reason: failureMessage, + details: deprecationNotice, + })), + createdMonitors: [], + updatedMonitors: [], + staleMonitors: [], + deletedMonitors: [], + failedStaleMonitors: [], + }); + subject?.complete(); }, }); diff --git a/x-pack/plugins/synthetics/server/routes/settings/add_param.ts b/x-pack/plugins/synthetics/server/routes/settings/add_param.ts index a563ee83d2d551..401dda0ce3045c 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/add_param.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/add_param.ts @@ -6,8 +6,9 @@ */ import { schema } from '@kbn/config-schema'; +import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; -import { SyntheticsParam } from '../../../common/runtime_types'; +import { SyntheticsParamRequest, SyntheticsParamSO } from '../../../common/runtime_types'; import { syntheticsParamType } from '../../../common/types/saved_objects'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -15,25 +16,35 @@ import { SYNTHETICS_API_URLS } from '../../../common/constants'; export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'POST', path: SYNTHETICS_API_URLS.PARAMS, - validate: { body: schema.object({ key: schema.string(), value: schema.string(), description: schema.maybe(schema.string()), tags: schema.maybe(schema.arrayOf(schema.string())), - namespaces: schema.maybe(schema.arrayOf(schema.string())), + share_across_spaces: schema.maybe(schema.boolean()), }), }, writeAccess: true, - handler: async ({ request, server, savedObjectsClient }): Promise => { - const { namespaces, ...data } = request.body as SyntheticsParam; - const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + handler: async ({ request, response, server, savedObjectsClient }): Promise => { + try { + const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { + id: DEFAULT_SPACE_ID, + }; + const { share_across_spaces: shareAcrossSpaces, ...data } = + request.body as SyntheticsParamRequest; - const result = await savedObjectsClient.create(syntheticsParamType, data, { - initialNamespaces: (namespaces ?? []).length > 0 ? namespaces : [spaceId], - }); + const result = await savedObjectsClient.create(syntheticsParamType, data, { + initialNamespaces: shareAcrossSpaces ? [ALL_SPACES_ID] : [spaceId], + }); + return { data: result }; + } catch (error) { + if (error.output?.statusCode === 404) { + const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } }); + } - return { data: result }; + throw error; + } }, }); diff --git a/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts b/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts index 82160111ef791d..2d1925f4c0182a 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts @@ -6,7 +6,8 @@ */ import { schema } from '@kbn/config-schema'; -import { SyntheticsParam } from '../../../common/runtime_types'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { SyntheticsParamRequest } from '../../../common/runtime_types'; import { syntheticsParamType } from '../../../common/types/saved_objects'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -21,15 +22,33 @@ export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ value: schema.string(), description: schema.maybe(schema.string()), tags: schema.maybe(schema.arrayOf(schema.string())), - namespaces: schema.maybe(schema.arrayOf(schema.string())), + share_across_spaces: schema.maybe(schema.boolean()), }), }, writeAccess: true, - handler: async ({ savedObjectsClient, request, server }): Promise => { - const { namespaces, id, ...data } = request.body as SyntheticsParam & { id: string }; + handler: async ({ savedObjectsClient, request, response, server }): Promise => { + try { + const { id: _spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { + id: DEFAULT_SPACE_ID, + }; + const { + share_across_spaces: shareAcrossSpaces, + id, + ...data + } = request.body as SyntheticsParamRequest & { + id: string; + }; - const result = await savedObjectsClient.update(syntheticsParamType, id, data); + const result = await savedObjectsClient.update(syntheticsParamType, id, data); - return { data: result }; + return { data: result }; + } catch (error) { + if (error.output?.statusCode === 404) { + const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } }); + } + + throw error; + } }, }); diff --git a/x-pack/plugins/synthetics/server/routes/settings/params.ts b/x-pack/plugins/synthetics/server/routes/settings/params.ts index e713ca7a33ffc2..31e6770e598a18 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/params.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/params.ts @@ -15,35 +15,46 @@ export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'GET', path: SYNTHETICS_API_URLS.PARAMS, validate: {}, - handler: async ({ savedObjectsClient, request, server }): Promise => { - const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient(); - - const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; - - const canSave = - (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false; - - if (canSave) { - const finder = - await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser({ + handler: async ({ savedObjectsClient, request, response, server }): Promise => { + try { + const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient(); + + const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { + id: DEFAULT_SPACE_ID, + }; + + const canSave = + (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false; + + if (canSave) { + const finder = + await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser({ + type: syntheticsParamType, + perPage: 1000, + namespaces: [spaceId], + }); + + const hits: SavedObjectsFindResult[] = []; + for await (const result of finder.find()) { + hits.push(...result.saved_objects); + } + + return { data: hits }; + } else { + const data = await savedObjectsClient.find({ type: syntheticsParamType, - perPage: 1000, - namespaces: [spaceId], + perPage: 10000, }); - const hits: SavedObjectsFindResult[] = []; - for await (const result of finder.find()) { - hits.push(...result.saved_objects); + return { data: data.saved_objects }; + } + } catch (error) { + if (error.output?.statusCode === 404) { + const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } }); } - return { data: hits }; - } else { - const data = await savedObjectsClient.find({ - type: syntheticsParamType, - perPage: 10000, - }); - - return { data: data.saved_objects }; + throw error; } }, }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts deleted file mode 100644 index a7ae725337b068..00000000000000 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts +++ /dev/null @@ -1,633 +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 { loggerMock } from '@kbn/logging-mocks'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { - INSUFFICIENT_FLEET_PERMISSIONS, - ProjectMonitorFormatterLegacy, -} from './project_monitor_formatter_legacy'; -import { ConfigKey, DataStream, LocationStatus } from '../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults'; -import { times } from 'lodash'; -import { SyntheticsService } from '../synthetics_service'; -import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; -import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; -import { SyntheticsMonitorClient } from '../synthetics_monitor/synthetics_monitor_client'; -import { httpServerMock } from '@kbn/core-http-server-mocks'; -import { Subject } from 'rxjs'; -import { formatSecrets } from '../utils'; - -import * as telemetryHooks from '../../routes/telemetry/monitor_upgrade_sender'; -import { formatLocation } from '../../../common/utils/location_formatter'; -import { mockEncryptedSO } from '../utils/mocks'; - -const testMonitors = [ - { - type: 'browser', - throttling: { download: 5, upload: 3, latency: 20 }, - schedule: 3, - locations: [], - privateLocations: ['Test private location'], - params: { url: 'http://localhost:8080' }, - playwrightOptions: { - userAgent: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - viewport: { width: 375, height: 667 }, - deviceScaleFactor: 2, - isMobile: true, - hasTouch: true, - headless: true, - }, - name: 'check if title is present 10 0', - id: 'check if title is present 10 0', - tags: [], - content: - 'UEsDBBQACAAIAAAAIQAAAAAAAAAAAAAAAAAQAAAAYmFzaWMuam91cm5leS50c2WQQU7DQAxF9znFV8QiUUOmXcCCUMQl2NdMnWbKJDMaO6Ilyt0JASQkNv9Z1teTZWNAIqwP5kU4iZGOug863u7uDXsSddbIddCOl0kMX6iPnsVoOAYxryTO1ucwpoGvtUrm+hiSYsLProIoxwp8iWwVM9oUeuTP/9V5k7UhofCscNhj2yx4xN2CzabElOHXWRxsx/YNroU69QwniImFB8Vui5vJzYcKxYRIJ66WTNQL5hL7p1WD9aYi9zQOtgPFGPNqecJ1sCj+tAB6J6erpj4FDcW3qh6TL5u1Mq/8yjn7BFBLBwhGDIWc4QAAAEkBAABQSwECLQMUAAgACAAAACEARgyFnOEAAABJAQAAEAAAAAAAAAAAACAApIEAAAAAYmFzaWMuam91cm5leS50c1BLBQYAAAAAAQABAD4AAAAfAQAAAAA=', - filter: { match: 'check if title is present 10 0' }, - hash: 'lleklrkelkj', - }, - { - type: 'browser', - throttling: { download: 5, upload: 3, latency: 20 }, - schedule: 3, - locations: [], - privateLocations: ['Test private location'], - params: { url: 'http://localhost:8080' }, - playwrightOptions: { - userAgent: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - viewport: { width: 375, height: 667 }, - deviceScaleFactor: 2, - isMobile: true, - hasTouch: true, - headless: true, - }, - name: 'check if title is present 10 1', - id: 'check if title is present 10 1', - tags: [], - content: - 'UEsDBBQACAAIAAAAIQAAAAAAAAAAAAAAAAAQAAAAYmFzaWMuam91cm5leS50c2WQQU7DQAxF9znFV8QiUUOmXcCCUMQl2NdMnWbKJDMaO6Ilyt0JASQkNv9Z1teTZWNAIqwP5kU4iZGOug863u7uDXsSddbIddCOl0kMX6iPnsVoOAYxryTO1ucwpoGvtUrm+hiSYsLProIoxwp8iWwVM9oUeuTP/9V5k7UhofCscNhj2yx4xN2CzabElOHXWRxsx/YNroU69QwniImFB8Vui5vJzYcKxYRIJ66WTNQL5hL7p1WD9aYi9zQOtgPFGPNqecJ1sCj+tAB6J6erpj4FDcW3qh6TL5u1Mq/8yjn7BFBLBwhGDIWc4QAAAEkBAABQSwECLQMUAAgACAAAACEARgyFnOEAAABJAQAAEAAAAAAAAAAAACAApIEAAAAAYmFzaWMuam91cm5leS50c1BLBQYAAAAAAQABAD4AAAAfAQAAAAA=', - filter: { match: 'check if title is present 10 1' }, - hash: 'lleklrkelkj', - }, -]; - -const privateLocations = times(1).map((n) => { - return { - id: `loc-${n}`, - label: 'Test private location', - geo: { - lat: 0, - lon: 0, - }, - isServiceManaged: false, - agentPolicyId: `loc-${n}`, - concurrentMonitors: 1, - }; -}); - -describe('ProjectMonitorFormatterLegacy', () => { - const mockEsClient = { - search: jest.fn(), - }; - const logger = loggerMock.create(); - - const kibanaRequest = httpServerMock.createKibanaRequest(); - - const soClient = savedObjectsClientMock.create(); - - const serverMock: UptimeServerSetup = { - logger, - uptimeEsClient: mockEsClient, - authSavedObjectsClient: soClient, - config: { - service: { - username: 'dev', - password: '12345', - manifestUrl: 'http://localhost:8080/api/manifest', - }, - }, - spaces: { - spacesService: { - getSpaceId: jest.fn().mockReturnValue('test-space'), - }, - }, - encryptedSavedObjects: mockEncryptedSO(), - } as unknown as UptimeServerSetup; - - const syntheticsService = new SyntheticsService(serverMock); - - syntheticsService.addConfig = jest.fn(); - syntheticsService.editConfig = jest.fn(); - syntheticsService.deleteConfigs = jest.fn(); - - const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createStart().getClient(); - - const locations = times(3).map((n) => { - return { - id: `loc-${n}`, - label: `Location ${n}`, - url: `https://example.com/${n}`, - geo: { - lat: 0, - lon: 0, - }, - isServiceManaged: true, - status: LocationStatus.GA, - }; - }); - - const monitorClient = new SyntheticsMonitorClient(syntheticsService, serverMock); - - it('should return errors', async () => { - const testSubject = new Subject(); - - testSubject.next = jest.fn(); - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId: 'test-project', - spaceId: 'default-space', - keepStale: false, - locations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient: soClient, - monitors: testMonitors, - server: serverMock, - syntheticsMonitorClient: monitorClient, - request: kibanaRequest, - subject: testSubject, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect(testSubject.next).toHaveBeenNthCalledWith( - 1, - 'check if title is present 10 0: failed to create or update monitor' - ); - expect(testSubject.next).toHaveBeenNthCalledWith( - 2, - 'check if title is present 10 1: failed to create or update monitor' - ); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }).toStrictEqual({ - createdMonitors: [], - deletedMonitors: [], - failedMonitors: [ - { - details: "Cannot read properties of undefined (reading 'authz')", - id: 'check if title is present 10 0', - payload: testMonitors[0], - reason: 'Failed to create or update monitor', - }, - { - details: "Cannot read properties of undefined (reading 'authz')", - id: 'check if title is present 10 1', - payload: testMonitors[1], - reason: 'Failed to create or update monitor', - }, - ], - failedStaleMonitors: [], - staleMonitors: [], - updatedMonitors: [], - }); - }); - - it('throws fleet permission error', async () => { - const testSubject = new Subject(); - - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: false } }), - }, - } as any; - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId: 'test-project', - spaceId: 'default-space', - keepStale: false, - locations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient: soClient, - monitors: testMonitors, - server: serverMock, - syntheticsMonitorClient: monitorClient, - request: kibanaRequest, - subject: testSubject, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }).toStrictEqual({ - createdMonitors: [], - deletedMonitors: [], - failedMonitors: [ - { - details: INSUFFICIENT_FLEET_PERMISSIONS, - id: 'check if title is present 10 0', - payload: testMonitors[0], - reason: 'Failed to create or update monitor', - }, - { - details: INSUFFICIENT_FLEET_PERMISSIONS, - id: 'check if title is present 10 1', - payload: testMonitors[1], - reason: 'Failed to create or update monitor', - }, - ], - failedStaleMonitors: [], - staleMonitors: [], - updatedMonitors: [], - }); - }); - - it('catches errors from bulk edit method', async () => { - const testSubject = new Subject(); - - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - } as any; - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId: 'test-project', - spaceId: 'default-space', - keepStale: false, - locations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient: soClient, - monitors: testMonitors, - server: serverMock, - syntheticsMonitorClient: monitorClient, - request: kibanaRequest, - subject: testSubject, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }).toEqual({ - createdMonitors: [], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [], - failedMonitors: [ - { - details: "Cannot read properties of undefined (reading 'buildPackagePolicyFromPackage')", - payload: payloadData, - reason: 'Failed to create 2 monitors', - }, - ], - failedStaleMonitors: [], - }); - }); - - it('configures project monitors when there are errors', async () => { - const testSubject = new Subject(); - - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - } as any; - - soClient.bulkCreate = jest.fn().mockResolvedValue({ saved_objects: [] }); - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId: 'test-project', - spaceId: 'default-space', - keepStale: false, - locations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient: soClient, - monitors: testMonitors, - server: serverMock, - syntheticsMonitorClient: monitorClient, - request: kibanaRequest, - subject: testSubject, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }).toEqual({ - createdMonitors: [], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [], - failedMonitors: [ - { - details: "Cannot read properties of undefined (reading 'buildPackagePolicyFromPackage')", - payload: payloadData, - reason: 'Failed to create 2 monitors', - }, - ], - failedStaleMonitors: [], - }); - }); - - it('shows errors thrown by fleet api', async () => { - const testSubject = new Subject(); - - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - packagePolicyService: {}, - } as any; - - soClient.bulkCreate = jest.fn().mockResolvedValue({ saved_objects: soResult }); - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId: 'test-project', - spaceId: 'default-space', - keepStale: false, - locations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient: soClient, - monitors: testMonitors, - server: serverMock, - syntheticsMonitorClient: monitorClient, - request: kibanaRequest, - subject: testSubject, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }).toEqual({ - createdMonitors: [], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [], - failedMonitors: [ - { - details: - 'this.server.fleet.packagePolicyService.buildPackagePolicyFromPackage is not a function', - reason: 'Failed to create 2 monitors', - payload: payloadData, - }, - ], - failedStaleMonitors: [], - }); - }); - - it('creates project monitors when no errors', async () => { - const testSubject = new Subject(); - - serverMock.fleet = { - authz: { - fromRequest: jest - .fn() - .mockResolvedValue({ integrations: { writeIntegrationPolicies: true } }), - }, - } as any; - - soClient.bulkCreate = jest.fn().mockResolvedValue({ saved_objects: soResult }); - - monitorClient.addMonitors = jest.fn().mockReturnValue({}); - - const telemetrySpy = jest - .spyOn(telemetryHooks, 'sendTelemetryEvents') - .mockImplementation(jest.fn()); - - const pushMonitorFormatter = new ProjectMonitorFormatterLegacy({ - projectId: 'test-project', - spaceId: 'default-space', - keepStale: false, - locations, - privateLocations, - encryptedSavedObjectsClient, - savedObjectsClient: soClient, - monitors: testMonitors, - server: serverMock, - syntheticsMonitorClient: monitorClient, - request: kibanaRequest, - subject: testSubject, - }); - - pushMonitorFormatter.getProjectMonitorsForProject = jest.fn().mockResolvedValue([]); - - await pushMonitorFormatter.configureAllProjectMonitors(); - - expect(soClient.bulkCreate).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - ...soData[0], - attributes: { - ...soData[0].attributes, - [ConfigKey.MONITOR_QUERY_ID]: expect.any(String), - [ConfigKey.CONFIG_ID]: expect.any(String), - }, - }), - expect.objectContaining({ - ...soData[1], - attributes: { - ...soData[1].attributes, - [ConfigKey.MONITOR_QUERY_ID]: expect.any(String), - [ConfigKey.CONFIG_ID]: expect.any(String), - }, - }), - ]) - ); - - expect(telemetrySpy).toHaveBeenCalledTimes(2); - - expect({ - createdMonitors: pushMonitorFormatter.createdMonitors, - updatedMonitors: pushMonitorFormatter.updatedMonitors, - staleMonitors: pushMonitorFormatter.staleMonitors, - deletedMonitors: pushMonitorFormatter.deletedMonitors, - failedMonitors: pushMonitorFormatter.failedMonitors, - failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors, - }).toEqual({ - createdMonitors: ['check if title is present 10 0', 'check if title is present 10 1'], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [], - failedMonitors: [], - failedStaleMonitors: [], - }); - }); -}); - -const payloadData = [ - { - ...DEFAULT_FIELDS[DataStream.BROWSER], - __ui: { - script_source: { - file_name: '', - is_generated_script: false, - }, - }, - config_id: '', - custom_heartbeat_id: 'check if title is present 10 0-test-project-default-space', - enabled: true, - 'filter_journeys.match': 'check if title is present 10 0', - 'filter_journeys.tags': [], - form_monitor_type: 'multistep', - ignore_https_errors: false, - journey_id: 'check if title is present 10 0', - locations: privateLocations.map((l) => formatLocation(l)), - name: 'check if title is present 10 0', - namespace: 'default_space', - origin: 'project', - original_space: 'default-space', - params: '{"url":"http://localhost:8080"}', - playwright_options: - '{"userAgent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1","viewport":{"width":375,"height":667},"deviceScaleFactor":2,"isMobile":true,"hasTouch":true,"headless":true}', - playwright_text_assertion: '', - project_id: 'test-project', - schedule: { - number: '3', - unit: 'm', - }, - screenshots: 'on', - 'service.name': '', - 'source.inline.script': '', - 'source.project.content': - 'UEsDBBQACAAIAAAAIQAAAAAAAAAAAAAAAAAQAAAAYmFzaWMuam91cm5leS50c2WQQU7DQAxF9znFV8QiUUOmXcCCUMQl2NdMnWbKJDMaO6Ilyt0JASQkNv9Z1teTZWNAIqwP5kU4iZGOug863u7uDXsSddbIddCOl0kMX6iPnsVoOAYxryTO1ucwpoGvtUrm+hiSYsLProIoxwp8iWwVM9oUeuTP/9V5k7UhofCscNhj2yx4xN2CzabElOHXWRxsx/YNroU69QwniImFB8Vui5vJzYcKxYRIJ66WTNQL5hL7p1WD9aYi9zQOtgPFGPNqecJ1sCj+tAB6J6erpj4FDcW3qh6TL5u1Mq/8yjn7BFBLBwhGDIWc4QAAAEkBAABQSwECLQMUAAgACAAAACEARgyFnOEAAABJAQAAEAAAAAAAAAAAACAApIEAAAAAYmFzaWMuam91cm5leS50c1BLBQYAAAAAAQABAD4AAAAfAQAAAAA=', - 'ssl.certificate': '', - 'ssl.certificate_authorities': '', - 'ssl.key': '', - 'ssl.key_passphrase': '', - 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], - 'ssl.verification_mode': 'full', - synthetics_args: [], - tags: [], - timeout: null, - type: 'browser', - 'url.port': null, - urls: '', - id: '', - hash: 'lleklrkelkj', - }, - { - ...DEFAULT_FIELDS[DataStream.BROWSER], - __ui: { - script_source: { - file_name: '', - is_generated_script: false, - }, - }, - config_id: '', - custom_heartbeat_id: 'check if title is present 10 1-test-project-default-space', - enabled: true, - 'filter_journeys.match': 'check if title is present 10 1', - 'filter_journeys.tags': [], - form_monitor_type: 'multistep', - ignore_https_errors: false, - journey_id: 'check if title is present 10 1', - locations: privateLocations.map((l) => formatLocation(l)), - name: 'check if title is present 10 1', - namespace: 'default_space', - origin: 'project', - original_space: 'default-space', - params: '{"url":"http://localhost:8080"}', - playwright_options: - '{"userAgent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1","viewport":{"width":375,"height":667},"deviceScaleFactor":2,"isMobile":true,"hasTouch":true,"headless":true}', - playwright_text_assertion: '', - project_id: 'test-project', - schedule: { - number: '3', - unit: 'm', - }, - screenshots: 'on', - 'service.name': '', - 'source.inline.script': '', - 'source.project.content': - 'UEsDBBQACAAIAAAAIQAAAAAAAAAAAAAAAAAQAAAAYmFzaWMuam91cm5leS50c2WQQU7DQAxF9znFV8QiUUOmXcCCUMQl2NdMnWbKJDMaO6Ilyt0JASQkNv9Z1teTZWNAIqwP5kU4iZGOug863u7uDXsSddbIddCOl0kMX6iPnsVoOAYxryTO1ucwpoGvtUrm+hiSYsLProIoxwp8iWwVM9oUeuTP/9V5k7UhofCscNhj2yx4xN2CzabElOHXWRxsx/YNroU69QwniImFB8Vui5vJzYcKxYRIJ66WTNQL5hL7p1WD9aYi9zQOtgPFGPNqecJ1sCj+tAB6J6erpj4FDcW3qh6TL5u1Mq/8yjn7BFBLBwhGDIWc4QAAAEkBAABQSwECLQMUAAgACAAAACEARgyFnOEAAABJAQAAEAAAAAAAAAAAACAApIEAAAAAYmFzaWMuam91cm5leS50c1BLBQYAAAAAAQABAD4AAAAfAQAAAAA=', - 'ssl.certificate': '', - 'ssl.certificate_authorities': '', - 'ssl.key': '', - 'ssl.key_passphrase': '', - 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], - 'ssl.verification_mode': 'full', - synthetics_args: [], - tags: [], - timeout: null, - type: 'browser', - 'url.port': null, - urls: '', - id: '', - hash: 'lleklrkelkj', - }, -]; - -const soData = [ - { - attributes: formatSecrets({ - ...payloadData[0], - revision: 1, - } as any), - type: 'synthetics-monitor', - }, - { - attributes: formatSecrets({ - ...payloadData[1], - revision: 1, - } as any), - type: 'synthetics-monitor', - }, -]; - -const soResult = soData.map((so) => ({ id: 'test-id', ...so })); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.ts deleted file mode 100644 index eb2daaadf8d848..00000000000000 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.ts +++ /dev/null @@ -1,539 +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 type { Subject } from 'rxjs'; -import { omit, isEqual } from 'lodash'; -import { KibanaRequest } from '@kbn/core/server'; -import { - SavedObjectsUpdateResponse, - SavedObjectsClientContract, - SavedObjectsFindResult, -} from '@kbn/core/server'; -import pMap from 'p-map'; -import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; -import { syncNewMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/add_monitor_bulk'; -import { deleteMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/delete_monitor_bulk'; -import { SyntheticsMonitorClient } from '../synthetics_monitor/synthetics_monitor_client'; -import { syncEditedMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/edit_monitor_bulk'; -import { - ConfigKey, - SyntheticsMonitorWithSecrets, - EncryptedSyntheticsMonitor, - ServiceLocationErrors, - ProjectMonitor, - Locations, - SyntheticsMonitor, - MonitorFields, - PrivateLocation, -} from '../../../common/runtime_types'; -import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; -import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; -import { formatSecrets, normalizeSecrets } from '../utils/secrets'; -import { - validateProjectMonitor, - validateMonitor, - ValidationResult, -} from '../../routes/monitor_cruds/monitor_validation'; -import { normalizeProjectMonitor } from './normalizers'; - -interface StaleMonitor { - stale: boolean; - journeyId: string; - savedObjectId: string; -} -type StaleMonitorMap = Record; -type FailedError = Array<{ id?: string; reason: string; details: string; payload?: object }>; - -export const INSUFFICIENT_FLEET_PERMISSIONS = - 'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.'; - -export class ProjectMonitorFormatterLegacy { - private projectId: string; - private spaceId: string; - private keepStale: boolean; - private locations: Locations; - private privateLocations: PrivateLocation[]; - private savedObjectsClient: SavedObjectsClientContract; - private encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - private staleMonitorsMap: StaleMonitorMap = {}; - private monitors: ProjectMonitor[] = []; - public createdMonitors: string[] = []; - public deletedMonitors: string[] = []; - public updatedMonitors: string[] = []; - public staleMonitors: string[] = []; - public failedMonitors: FailedError = []; - public failedStaleMonitors: FailedError = []; - private server: UptimeServerSetup; - private projectFilter: string; - private syntheticsMonitorClient: SyntheticsMonitorClient; - private request: KibanaRequest; - private subject?: Subject; - - private writeIntegrationPoliciesPermissions?: boolean; - - constructor({ - locations, - privateLocations, - keepStale, - savedObjectsClient, - encryptedSavedObjectsClient, - projectId, - spaceId, - monitors, - server, - syntheticsMonitorClient, - request, - subject, - }: { - locations: Locations; - privateLocations: PrivateLocation[]; - keepStale: boolean; - savedObjectsClient: SavedObjectsClientContract; - encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - projectId: string; - spaceId: string; - monitors: ProjectMonitor[]; - server: UptimeServerSetup; - syntheticsMonitorClient: SyntheticsMonitorClient; - request: KibanaRequest; - subject?: Subject; - }) { - this.projectId = projectId; - this.spaceId = spaceId; - this.locations = locations; - this.privateLocations = privateLocations; - this.keepStale = keepStale; - this.savedObjectsClient = savedObjectsClient; - this.encryptedSavedObjectsClient = encryptedSavedObjectsClient; - this.syntheticsMonitorClient = syntheticsMonitorClient; - this.monitors = monitors; - this.server = server; - this.projectFilter = `${syntheticsMonitorType}.attributes.${ConfigKey.PROJECT_ID}: "${this.projectId}"`; - this.request = request; - this.subject = subject; - } - - public configureAllProjectMonitors = async () => { - const existingMonitors = await this.getProjectMonitorsForProject(); - this.staleMonitorsMap = await this.getStaleMonitorsMap(existingMonitors); - - const normalizedNewMonitors: SyntheticsMonitor[] = []; - const normalizedUpdateMonitors: Array<{ - previousMonitor: SavedObjectsFindResult; - monitor: SyntheticsMonitor; - }> = []; - - for (const monitor of this.monitors) { - const previousMonitor = existingMonitors.find( - (monitorObj) => - (monitorObj.attributes as SyntheticsMonitor)[ConfigKey.JOURNEY_ID] === monitor.id - ); - - const normM = await this.validateProjectMonitor({ - monitor, - }); - - if (normM) { - if (previousMonitor) { - this.updatedMonitors.push(monitor.id); - if (this.staleMonitorsMap[monitor.id]) { - this.staleMonitorsMap[monitor.id].stale = false; - } - normalizedUpdateMonitors.push({ monitor: normM as MonitorFields, previousMonitor }); - } else { - normalizedNewMonitors.push(normM as MonitorFields); - } - } - } - - await this.createMonitorsBulk(normalizedNewMonitors); - - const { updatedCount } = await this.updateMonitorsBulk(normalizedUpdateMonitors); - - if (normalizedUpdateMonitors.length > 0) { - let updateMessage = ''; - if (updatedCount > 0) { - updateMessage = `${updatedCount} monitor${ - updatedCount > 1 ? 's' : '' - } updated successfully.`; - } - - const noChanges = normalizedUpdateMonitors.length - updatedCount; - let noChangeMessage = ''; - if (noChanges > 0) { - noChangeMessage = `${noChanges} monitor${noChanges > 1 ? 's' : ''} found with no changes.`; - } - - this.handleStreamingMessage({ - message: `${updateMessage} ${noChangeMessage}`, - }); - } - - await this.handleStaleMonitors(); - }; - - validatePermissions = async ({ monitor }: { monitor: ProjectMonitor }) => { - if (this.writeIntegrationPoliciesPermissions || (monitor.privateLocations ?? []).length === 0) { - return; - } - const { - integrations: { writeIntegrationPolicies }, - } = await this.server.fleet.authz.fromRequest(this.request); - - this.writeIntegrationPoliciesPermissions = writeIntegrationPolicies; - - if (!writeIntegrationPolicies) { - throw new Error(INSUFFICIENT_FLEET_PERMISSIONS); - } - }; - - validateProjectMonitor = async ({ monitor }: { monitor: ProjectMonitor }) => { - try { - await this.validatePermissions({ monitor }); - - const { normalizedFields: normalizedMonitor, errors } = normalizeProjectMonitor({ - monitor, - locations: this.locations, - privateLocations: this.privateLocations, - projectId: this.projectId, - namespace: this.spaceId, - version: this.server.stackVersion, - }); - - if (errors.length) { - this.failedMonitors.push(...errors); - this.handleStreamingMessage({ - message: `${monitor.id}: failed to create or update monitor`, - }); - return null; - } - - /* Validates that the payload sent from the synthetics agent is valid */ - const { valid: isMonitorPayloadValid } = this.validateMonitor({ - validationResult: validateProjectMonitor( - { - ...monitor, - type: normalizedMonitor[ConfigKey.MONITOR_TYPE], - }, - this.locations, - this.privateLocations - ), - monitorId: monitor.id, - }); - - if (!isMonitorPayloadValid) { - return null; - } - - /* Validates that the normalized monitor is a valid monitor saved object type */ - const { valid: isNormalizedMonitorValid, decodedMonitor } = this.validateMonitor({ - validationResult: validateMonitor(normalizedMonitor as MonitorFields), - monitorId: monitor.id, - }); - - if (!isNormalizedMonitorValid || !decodedMonitor) { - return null; - } - - return decodedMonitor; - } catch (e) { - this.server.logger.error(e); - this.failedMonitors.push({ - id: monitor.id, - reason: 'Failed to create or update monitor', - details: e.message, - payload: monitor, - }); - this.handleStreamingMessage({ message: `${monitor.id}: failed to create or update monitor` }); - if (this.staleMonitorsMap[monitor.id]) { - this.staleMonitorsMap[monitor.id].stale = false; - } - } - }; - - private getStaleMonitorsMap = async ( - existingMonitors: Array> - ): Promise => { - const staleMonitors: StaleMonitorMap = {}; - - existingMonitors.forEach((savedObject) => { - const journeyId = (savedObject.attributes as SyntheticsMonitor)[ConfigKey.JOURNEY_ID]; - if (journeyId) { - staleMonitors[journeyId] = { - stale: true, - savedObjectId: savedObject.id, - journeyId, - }; - } - }); - - return staleMonitors; - }; - - public getProjectMonitorsForProject = async () => { - const finder = this.savedObjectsClient.createPointInTimeFinder({ - type: syntheticsMonitorType, - perPage: 1000, - filter: this.projectFilter, - }); - - const hits: Array> = []; - for await (const result of finder.find()) { - hits.push( - ...(result.saved_objects as Array>) - ); - } - - await finder.close(); - - return hits; - }; - - private createMonitorsBulk = async (monitors: SyntheticsMonitor[]) => { - try { - if (monitors.length > 0) { - const { newMonitors } = await syncNewMonitorBulk({ - normalizedMonitors: monitors, - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - soClient: this.savedObjectsClient, - request: this.request, - privateLocations: this.privateLocations, - spaceId: this.spaceId, - }); - - if (newMonitors && newMonitors.length === monitors.length) { - this.createdMonitors.push(...monitors.map((monitor) => monitor[ConfigKey.JOURNEY_ID]!)); - this.handleStreamingMessage({ - message: `${monitors.length} monitor${ - monitors.length > 1 ? 's' : '' - } created successfully.`, - }); - } else { - this.failedMonitors.push({ - reason: `Failed to create ${monitors.length} monitors`, - details: 'Failed to create monitors', - payload: monitors, - }); - this.handleStreamingMessage({ - message: `Failed to create ${monitors.length} monitors`, - }); - } - } - } catch (e) { - this.server.logger.error(e); - this.failedMonitors.push({ - reason: `Failed to create ${monitors.length} monitors`, - details: e.message, - payload: monitors, - }); - this.handleStreamingMessage({ - message: `Failed to create ${monitors.length} monitors`, - }); - } - }; - - private getDecryptedMonitors = async ( - monitors: Array> - ) => { - return await pMap( - monitors, - async (monitor) => - this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( - syntheticsMonitorType, - monitor.id, - { - namespace: monitor.namespaces?.[0], - } - ), - { concurrency: 500 } - ); - }; - - private updateMonitorsBulk = async ( - monitors: Array<{ - monitor: SyntheticsMonitor; - previousMonitor: SavedObjectsFindResult; - }> - ): Promise<{ - editedMonitors: Array>; - errors: ServiceLocationErrors; - updatedCount: number; - }> => { - const decryptedPreviousMonitors = await this.getDecryptedMonitors( - monitors.map((m) => m.previousMonitor) - ); - - const monitorsToUpdate = []; - - for (let i = 0; i < decryptedPreviousMonitors.length; i++) { - const decryptedPreviousMonitor = decryptedPreviousMonitors[i]; - const previousMonitor = monitors[i].previousMonitor; - const normalizedMonitor = monitors[i].monitor; - - const keysToOmit = [ConfigKey.REVISION, ConfigKey.MONITOR_QUERY_ID, ConfigKey.CONFIG_ID]; - const { attributes: normalizedPreviousMonitorAttributes } = - normalizeSecrets(decryptedPreviousMonitor); - const hasMonitorBeenEdited = !isEqual( - omit(normalizedMonitor, keysToOmit), - omit(normalizedPreviousMonitorAttributes, keysToOmit) - ); - - if (hasMonitorBeenEdited) { - const monitorWithRevision = formatSecrets({ - ...normalizedPreviousMonitorAttributes, - ...normalizedMonitor, - revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, - }); - monitorsToUpdate.push({ - normalizedMonitor, - previousMonitor, - monitorWithRevision, - decryptedPreviousMonitor, - }); - } - } - - const { editedMonitors } = await syncEditedMonitorBulk({ - monitorsToUpdate, - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - savedObjectsClient: this.savedObjectsClient, - request: this.request, - privateLocations: this.privateLocations, - spaceId: this.spaceId, - }); - return { - editedMonitors: editedMonitors ?? [], - errors: [], - updatedCount: monitorsToUpdate.length, - }; - }; - - private handleStaleMonitors = async () => { - try { - const staleMonitorsList = Object.values(this.staleMonitorsMap).filter( - (monitor) => monitor.stale === true - ); - - const encryptedMonitors = await this.savedObjectsClient.bulkGet( - staleMonitorsList.map((staleMonitor) => ({ - id: staleMonitor.savedObjectId, - type: syntheticsMonitorType, - })) - ); - - let monitors = encryptedMonitors.saved_objects; - - const hasPrivateMonitor = monitors.some((monitor) => - monitor.attributes.locations.some((location) => !location.isServiceManaged) - ); - - if (hasPrivateMonitor) { - const { - integrations: { writeIntegrationPolicies }, - } = await this.server.fleet.authz.fromRequest(this.request); - if (!writeIntegrationPolicies) { - monitors = monitors.filter((monitor) => { - const hasPrivateLocation = monitor.attributes.locations.some( - (location) => !location.isServiceManaged - ); - if (hasPrivateLocation) { - const journeyId = (monitor.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; - const monitorName = (monitor.attributes as MonitorFields)[ConfigKey.NAME]!; - this.handleStreamingMessage({ - message: `Monitor ${journeyId} could not be deleted`, - }); - this.failedStaleMonitors.push({ - id: journeyId, - reason: 'Failed to delete stale monitor', - details: `Unable to delete Synthetics package policy for monitor ${monitorName}. Fleet write permissions are needed to use Synthetics private locations.`, - }); - } - return !hasPrivateLocation; - }); - } - } - - const chunkSize = 100; - for (let i = 0; i < monitors.length; i += chunkSize) { - const chunkMonitors = monitors.slice(i, i + chunkSize); - try { - if (!this.keepStale) { - await deleteMonitorBulk({ - monitors: chunkMonitors, - savedObjectsClient: this.savedObjectsClient, - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - request: this.request, - }); - - for (const sm of chunkMonitors) { - const journeyId = (sm.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; - - this.deletedMonitors.push(journeyId); - this.handleStreamingMessage({ - message: `Monitor ${journeyId} deleted successfully`, - }); - } - } else { - chunkMonitors.forEach((sm) => { - const journeyId = (sm.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; - this.staleMonitors.push(journeyId); - }); - } - } catch (e) { - chunkMonitors.forEach((sm) => { - const journeyId = (sm.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; - - this.handleStreamingMessage({ - message: `Monitor ${journeyId} could not be deleted`, - }); - this.failedStaleMonitors.push({ - id: journeyId, - reason: 'Failed to delete stale monitor', - details: e.message, - payload: staleMonitorsList.find( - (staleMonitor) => staleMonitor.savedObjectId === sm.id - ), - }); - }); - this.server.logger.error(e); - } - } - } catch (e) { - this.server.logger.error(e); - } - }; - - private handleStreamingMessage = ({ message }: { message: string }) => { - if (this.subject) { - this.subject?.next(message); - } - }; - - private validateMonitor = ({ - validationResult, - monitorId, - }: { - validationResult: ValidationResult; - monitorId: string; - }) => { - const { reason: message, details, payload: validationPayload, valid } = validationResult; - if (!valid) { - this.failedMonitors.push({ - id: monitorId, - reason: message, - details, - payload: validationPayload, - }); - if (this.staleMonitorsMap[monitorId]) { - this.staleMonitorsMap[monitorId].stale = false; - } - } - return validationResult; - }; -} diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index e4ef7cbbb6e67c..3e420bf478decb 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -42,7 +42,7 @@ import { ServiceLocations, SyntheticsMonitorWithId, SyntheticsMonitorWithSecrets, - SyntheticsParam, + SyntheticsParamSO, ThrottlingOptions, } from '../../common/runtime_types'; import { getServiceLocations } from './get_service_locations'; @@ -555,10 +555,10 @@ export class SyntheticsService { async getSyntheticsParams({ spaceId }: { spaceId?: string } = {}) { const encryptedClient = this.server.encryptedSavedObjects.getClient(); - const paramsBySpace: Record> = {}; + const paramsBySpace: Record> = Object.create(null); const finder = - await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser({ + await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser({ type: syntheticsParamType, perPage: 1000, namespaces: spaceId ? [spaceId] : undefined, @@ -568,7 +568,7 @@ export class SyntheticsService { response.saved_objects.forEach((param) => { param.namespaces?.forEach((namespace) => { if (!paramsBySpace[namespace]) { - paramsBySpace[namespace] = {}; + paramsBySpace[namespace] = Object.create(null); } paramsBySpace[namespace][param.attributes.key] = param.attributes.value; }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 007b67ef87e4d9..b7b73bf88696f6 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -34235,7 +34235,6 @@ "xpack.synthetics.synthetics.waterfall.requestsTotalMessage.first": "{count} premier(s)", "xpack.synthetics.tableTitle.showing": "Affichage de {count} sur {total} {label}", "xpack.synthetics.tagsList.filter": "Cliquez pour filtrer la liste avec la balise {tag}", - "xpack.synthetics.testRun.runErrorLocation": "Impossible d'exécuter le moniteur sur l'emplacement {locationName}.", "xpack.synthetics.testRunDetailsRoute.title": "Détails de l'exécution du test | {baseTitle}", "xpack.synthetics.waterfall.networkRequests.count": "Affichage de {countShown} sur {total} {networkRequestsLabel}", "xpack.synthetics.waterfall.networkRequests.pluralizedCount": "{total, plural, one {requête réseau} other {requêtes réseau}}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5632c75886cf76..2c0b9f632acc90 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34214,7 +34214,6 @@ "xpack.synthetics.synthetics.waterfall.requestsTotalMessage.first": "最初の{count}件", "xpack.synthetics.tableTitle.showing": "{total}件の{label}中{count}件を表示中", "xpack.synthetics.tagsList.filter": "クリックして、タグ{tag}を使用して一覧をフィルターします", - "xpack.synthetics.testRun.runErrorLocation": "場所{locationName}でモニターを実行できませんでした。", "xpack.synthetics.testRunDetailsRoute.title": "テスト実行詳細 | {baseTitle}", "xpack.synthetics.waterfall.networkRequests.count": "{total}件の{networkRequestsLabel}中{countShown}件を表示中", "xpack.synthetics.waterfall.networkRequests.pluralizedCount": "{total, plural, other {ネットワークリクエスト}}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c58a2c54778da3..18e59de9cedbf0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34231,7 +34231,6 @@ "xpack.synthetics.synthetics.waterfall.requestsTotalMessage.first": "前 {count} 个", "xpack.synthetics.tableTitle.showing": "正在显示第 {count} 个(共 {total} 个){label}", "xpack.synthetics.tagsList.filter": "单击以筛选带 {tag} 标签的列表", - "xpack.synthetics.testRun.runErrorLocation": "无法在位置 {locationName} 运行监测。", "xpack.synthetics.testRunDetailsRoute.title": "测试运行详情 | {baseTitle}", "xpack.synthetics.waterfall.networkRequests.count": "正在显示第 {countShown} 个(共 {total} 个){networkRequestsLabel}", "xpack.synthetics.waterfall.networkRequests.pluralizedCount": "{total, plural, other {网络请求}}", diff --git a/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts b/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts new file mode 100644 index 00000000000000..678cee4463a9a5 --- /dev/null +++ b/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts @@ -0,0 +1,278 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; +import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import expect from '@kbn/expect'; +import { syntheticsParamType } from '@kbn/synthetics-plugin/common/types/saved_objects'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + describe('AddEditParams', function () { + this.tags('skipCloud'); + const supertestAPI = getService('supertest'); + const kServer = getService('kibanaServer'); + const testParam = { + key: 'test', + value: 'test', + }; + + before(async () => { + await supertestAPI.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); + await supertestAPI + .post('/api/fleet/epm/packages/synthetics/0.12.0') + .set('kbn-xsrf', 'true') + .send({ force: true }) + .expect(200); + + await kServer.savedObjects.clean({ types: [syntheticsParamType] }); + }); + + it('adds a test param', async () => { + await supertestAPI + .post(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(200); + + const getResponse = await supertestAPI + .get(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(getResponse.body.data[0].attributes).eql(testParam); + }); + + it('handles tags and description', async () => { + const tagsAndDescription = { + tags: ['a tag'], + description: 'test description', + }; + const testParam2 = { + ...testParam, + ...tagsAndDescription, + }; + await supertestAPI + .post(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .send(testParam2) + .expect(200); + + const getResponse = await supertestAPI + .get(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(getResponse.body.data[0].attributes).eql(testParam2); + }); + + it('handles editing a param', async () => { + const updatedParam = { + key: 'testUpdated', + value: 'testUpdated', + tags: ['a tag'], + description: 'test description', + }; + + await supertestAPI + .post(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(200); + + const getResponse = await supertestAPI + .get(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .expect(200); + const param = getResponse.body.data[0]; + expect(param.attributes).eql(testParam); + + await supertestAPI + .put(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .send({ ...updatedParam, id: param.id }) + .expect(200); + + const updatedGetResponse = await supertestAPI + .get(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .expect(200); + const updatedParamSO = updatedGetResponse.body.data[0]; + expect(updatedParamSO.attributes).eql(updatedParam); + }); + + it('handles spaces', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + + await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + + await supertestAPI + .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(200); + + const getResponse = await supertestAPI + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(getResponse.body.data[0].namespaces).eql([SPACE_ID]); + expect(getResponse.body.data[0].attributes).eql(testParam); + }); + + it('handles editing a param in spaces', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + + await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + + const updatedParam = { + key: 'testUpdated', + value: 'testUpdated', + tags: ['a tag'], + description: 'test description', + }; + + await supertestAPI + .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(200); + + const getResponse = await supertestAPI + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + const param = getResponse.body.data[0]; + expect(param.attributes).eql(testParam); + + await supertestAPI + .put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send({ ...updatedParam, id: param.id }) + .expect(200); + + const updatedGetResponse = await supertestAPI + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + const updatedParamSO = updatedGetResponse.body.data[0]; + expect(updatedParamSO.attributes).eql(updatedParam); + }); + + it('does not allow editing a param in created in one space in a different space', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + const SPACE_ID_TWO = `test-space-${uuidv4()}-two`; + const SPACE_NAME_TWO = `test-space-name ${uuidv4()} 2`; + + await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + await kServer.spaces.create({ id: SPACE_ID_TWO, name: SPACE_NAME_TWO }); + + const updatedParam = { + key: 'testUpdated', + value: 'testUpdated', + tags: ['a tag'], + description: 'test description', + }; + + await supertestAPI + .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(200); + + const getResponse = await supertestAPI + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + const param = getResponse.body.data[0]; + expect(param.attributes).eql(testParam); + + // space does exist so get request should be 200 + await supertestAPI + .get(`/s/${SPACE_ID_TWO}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + + await supertestAPI + .put(`/s/${SPACE_ID_TWO}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send({ ...updatedParam, id: param.id }) + .expect(404); + + const updatedGetResponse = await supertestAPI + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + const updatedParamSO = updatedGetResponse.body.data[0]; + expect(updatedParamSO.attributes).eql(testParam); + }); + + it('handles invalid spaces', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + + await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + + await supertestAPI + .post(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(404); + }); + + it('handles editing with invalid spaces', async () => { + const updatedParam = { + key: 'testUpdated', + value: 'testUpdated', + tags: ['a tag'], + description: 'test description', + }; + + await supertestAPI + .post(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .send(testParam) + .expect(200); + const getResponse = await supertestAPI + .get(SYNTHETICS_API_URLS.PARAMS) + .set('kbn-xsrf', 'true') + .expect(200); + const param = getResponse.body.data[0]; + expect(param.attributes).eql(testParam); + + await supertestAPI + .put(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send({ ...updatedParam, id: param.id }) + .expect(404); + }); + + it('handles share across spaces', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + + await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + + await supertestAPI + .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .send({ ...testParam, share_across_spaces: true }) + .expect(200); + + const getResponse = await supertestAPI + .get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(getResponse.body.data[0].namespaces).eql(['*']); + expect(getResponse.body.data[0].attributes).eql(testParam); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index 7674d2fbc534fe..632e3473024183 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -1758,9 +1758,9 @@ export default function ({ getService }: FtrProviderContext) { expect(message).to.eql(REQUEST_TOO_LARGE); } finally { await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) .set('kbn-xsrf', 'true') - .send({ ...projectMonitors, keep_stale: false, project }); + .send({ ...projectMonitors, project }); } }); diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts deleted file mode 100644 index 0e2cec6c3a5c35..00000000000000 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts +++ /dev/null @@ -1,2178 +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 fetch, { BodyInit, HeadersInit, Response } from 'node-fetch'; -import { v4 as uuidv4 } from 'uuid'; -import expect from '@kbn/expect'; -import { format as formatUrl } from 'url'; -import { - ConfigKey, - LegacyProjectMonitorsRequest, -} from '@kbn/synthetics-plugin/common/runtime_types'; -import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; -import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; -import { PackagePolicy } from '@kbn/fleet-plugin/common'; -import { - PROFILE_VALUES_ENUM, - PROFILES_MAP, -} from '@kbn/synthetics-plugin/common/constants/monitor_defaults'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; -import { PrivateLocationTestService } from './services/private_location_test_service'; -import { comparePolicies } from './sample_data/test_policy'; -import { getTestProjectSyntheticsPolicy } from './sample_data/test_project_monitor_policy'; - -export default function ({ getService }: FtrProviderContext) { - describe('AddProjectLegacyMonitors', function () { - this.tags('skipCloud'); - - const supertest = getService('supertest'); - const config = getService('config'); - const kibanaServerUrl = formatUrl(config.get('servers.kibana')); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const security = getService('security'); - const kibanaServer = getService('kibanaServer'); - const projectMonitorEndpoint = kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY; - - let projectMonitors: LegacyProjectMonitorsRequest; - let httpProjectMonitors: LegacyProjectMonitorsRequest; - let tcpProjectMonitors: LegacyProjectMonitorsRequest; - let icmpProjectMonitors: LegacyProjectMonitorsRequest; - - let testPolicyId = ''; - const testPrivateLocations = new PrivateLocationTestService(getService); - - const setUniqueIds = (request: LegacyProjectMonitorsRequest) => { - return { - ...request, - monitors: request.monitors.map((monitor) => ({ ...monitor, id: uuidv4() })), - }; - }; - - const deleteMonitor = async ( - journeyId: string, - projectId: string, - space: string = 'default', - username: string = '', - password: string = '' - ) => { - try { - const response = await supertest - .get(`/s/${space}${API_URLS.SYNTHETICS_MONITORS}`) - .auth(username, password) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: "${journeyId}" AND ${syntheticsMonitorType}.attributes.project_id: "${projectId}"`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - const { monitors } = response.body; - if (monitors[0]?.id) { - await supertest - .delete(`/s/${space}${API_URLS.SYNTHETICS_MONITORS}/${monitors[0].id}`) - .set('kbn-xsrf', 'true') - .send(projectMonitors) - .expect(200); - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - } - }; - - before(async () => { - await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); - await supertest - .post('/api/fleet/epm/packages/synthetics/0.12.0') - .set('kbn-xsrf', 'true') - .send({ force: true }) - .expect(200); - - const testPolicyName = 'Fleet test server policy' + Date.now(); - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); - }); - - beforeEach(() => { - projectMonitors = setUniqueIds(getFixtureJson('project_browser_monitor')); - httpProjectMonitors = setUniqueIds(getFixtureJson('project_http_monitor')); - tcpProjectMonitors = setUniqueIds(getFixtureJson('project_tcp_monitor')); - icmpProjectMonitors = setUniqueIds(getFixtureJson('project_icmp_monitor')); - }); - - it('project monitors - handles browser monitors', async () => { - const successfulMonitors = [projectMonitors.monitors[0]]; - - try { - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(projectMonitors) - ); - - expect(messages).to.have.length(2); - expect(messages[1].updatedMonitors).eql([]); - expect(messages[1].createdMonitors).eql(successfulMonitors.map((monitor) => monitor.id)); - expect(messages[1].failedMonitors).eql([]); - - for (const monitor of successfulMonitors) { - const journeyId = monitor.id; - const createdMonitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${journeyId}` }) - .set('kbn-xsrf', 'true') - .expect(200); - - const decryptedCreatedMonitor = await supertest - .get(`${API_URLS.SYNTHETICS_MONITORS}/${createdMonitorsResponse.body.monitors[0].id}`) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(decryptedCreatedMonitor.body.attributes).to.eql({ - __ui: { - script_source: { - file_name: '', - is_generated_script: false, - }, - }, - config_id: decryptedCreatedMonitor.body.id, - custom_heartbeat_id: `${journeyId}-test-suite-default`, - enabled: true, - alert: { - status: { - enabled: true, - }, - }, - 'filter_journeys.match': 'check if title is present', - 'filter_journeys.tags': [], - form_monitor_type: 'multistep', - ignore_https_errors: false, - journey_id: journeyId, - locations: [ - { - geo: { - lat: 0, - lon: 0, - }, - id: 'localhost', - isServiceManaged: true, - label: 'Local Synthetics Service', - }, - ], - name: 'check if title is present', - namespace: 'default', - origin: 'project', - original_space: 'default', - playwright_options: '{"headless":true,"chromiumSandbox":false}', - playwright_text_assertion: '', - project_id: 'test-suite', - params: '', - revision: 1, - schedule: { - number: '10', - unit: 'm', - }, - screenshots: 'on', - 'service.name': '', - synthetics_args: [], - tags: [], - throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], - 'ssl.certificate': '', - 'ssl.certificate_authorities': '', - 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], - 'ssl.verification_mode': 'full', - 'ssl.key': '', - 'ssl.key_passphrase': '', - 'source.inline.script': '', - 'source.project.content': - 'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA', - timeout: null, - type: 'browser', - 'url.port': null, - urls: '', - id: `${journeyId}-test-suite-default`, - hash: 'ekrjelkjrelkjre', - }); - } - } finally { - await Promise.all([ - successfulMonitors.map((monitor) => { - return deleteMonitor(monitor.id, httpProjectMonitors.project); - }), - ]); - } - }); - - it('project monitors - handles http monitors', async () => { - const kibanaVersion = await kibanaServer.version.get(); - const successfulMonitors = [httpProjectMonitors.monitors[1]]; - - try { - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(httpProjectMonitors) - ); - - expect(messages).to.have.length(3); - expect(messages[2].updatedMonitors).eql([]); - expect(messages[2].createdMonitors).eql(successfulMonitors.map((monitor) => monitor.id)); - expect(messages[2].failedMonitors).eql([ - { - id: httpProjectMonitors.monitors[0].id, - details: `\`http\` project monitors must have exactly one value for field \`urls\` in version \`${kibanaVersion}\`. Your monitor was not created or updated.`, - reason: 'Invalid Heartbeat configuration', - }, - { - id: httpProjectMonitors.monitors[0].id, - details: `The following Heartbeat options are not supported for ${httpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: check.response.body|unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.`, - reason: 'Unsupported Heartbeat option', - }, - ]); - - for (const monitor of successfulMonitors) { - const journeyId = monitor.id; - const isTLSEnabled = Object.keys(monitor).some((key) => key.includes('ssl')); - const createdMonitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${journeyId}` }) - .set('kbn-xsrf', 'true') - .expect(200); - - const decryptedCreatedMonitor = await supertest - .get(`${API_URLS.SYNTHETICS_MONITORS}/${createdMonitorsResponse.body.monitors[0].id}`) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(decryptedCreatedMonitor.body.attributes).to.eql({ - __ui: { - is_tls_enabled: isTLSEnabled, - }, - 'check.request.method': 'POST', - 'check.response.status': ['200'], - config_id: decryptedCreatedMonitor.body.id, - custom_heartbeat_id: `${journeyId}-test-suite-default`, - 'check.response.body.negative': [], - 'check.response.body.positive': ['Saved', 'saved'], - 'check.response.json': [ - { description: 'check status', expression: 'foo.bar == "myValue"' }, - ], - 'check.response.headers': {}, - 'check.request.body': { - type: 'text', - value: '', - }, - 'check.request.headers': { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - enabled: false, - alert: { - status: { - enabled: true, - }, - }, - form_monitor_type: 'http', - journey_id: journeyId, - locations: [ - { - geo: { - lat: 0, - lon: 0, - }, - id: 'localhost', - isServiceManaged: true, - label: 'Local Synthetics Service', - }, - ], - max_redirects: '0', - name: monitor.name, - namespace: 'default', - origin: 'project', - original_space: 'default', - project_id: 'test-suite', - username: '', - password: '', - proxy_url: '', - proxy_headers: {}, - 'response.include_body': 'always', - 'response.include_headers': false, - 'response.include_body_max_bytes': '900', - revision: 1, - schedule: { - number: '60', - unit: 'm', - }, - 'service.name': '', - 'ssl.certificate': '', - 'ssl.certificate_authorities': '', - 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], - 'ssl.verification_mode': isTLSEnabled ? 'strict' : 'full', - 'ssl.key': '', - 'ssl.key_passphrase': '', - tags: Array.isArray(monitor.tags) ? monitor.tags : monitor.tags?.split(','), - timeout: '80', - type: 'http', - urls: Array.isArray(monitor.urls) ? monitor.urls?.[0] : monitor.urls, - 'url.port': null, - id: `${journeyId}-test-suite-default`, - hash: 'ekrjelkjrelkjre', - ipv6: true, - ipv4: true, - mode: 'any', - }); - } - } finally { - await Promise.all([ - successfulMonitors.map((monitor) => { - return deleteMonitor(monitor.id, httpProjectMonitors.project); - }), - ]); - } - }); - - it('project monitors - handles tcp monitors', async () => { - const successfulMonitors = [tcpProjectMonitors.monitors[0], tcpProjectMonitors.monitors[1]]; - const kibanaVersion = await kibanaServer.version.get(); - - try { - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(tcpProjectMonitors) - ); - - expect(messages).to.have.length(3); - expect(messages[2].updatedMonitors).eql([]); - expect(messages[2].createdMonitors).eql(successfulMonitors.map((monitor) => monitor.id)); - expect(messages[2].failedMonitors).eql([ - { - id: tcpProjectMonitors.monitors[2].id, - details: `\`tcp\` project monitors must have exactly one value for field \`hosts\` in version \`${kibanaVersion}\`. Your monitor was not created or updated.`, - reason: 'Invalid Heartbeat configuration', - }, - { - id: tcpProjectMonitors.monitors[2].id, - details: `The following Heartbeat options are not supported for ${tcpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: ports|unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.`, - reason: 'Unsupported Heartbeat option', - }, - ]); - - for (const monitor of successfulMonitors) { - const journeyId = monitor.id; - const isTLSEnabled = Object.keys(monitor).some((key) => key.includes('ssl')); - const createdMonitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${journeyId}` }) - .set('kbn-xsrf', 'true') - .expect(200); - - const decryptedCreatedMonitor = await supertest - .get(`${API_URLS.SYNTHETICS_MONITORS}/${createdMonitorsResponse.body.monitors[0].id}`) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(decryptedCreatedMonitor.body.attributes).to.eql({ - __ui: { - is_tls_enabled: isTLSEnabled, - }, - config_id: decryptedCreatedMonitor.body.id, - custom_heartbeat_id: `${journeyId}-test-suite-default`, - 'check.receive': '', - 'check.send': '', - enabled: true, - alert: { - status: { - enabled: true, - }, - }, - form_monitor_type: 'tcp', - journey_id: journeyId, - locations: [ - { - geo: { - lat: 0, - lon: 0, - }, - id: 'localhost', - isServiceManaged: true, - label: 'Local Synthetics Service', - }, - ], - name: monitor.name, - namespace: 'default', - origin: 'project', - original_space: 'default', - project_id: 'test-suite', - revision: 1, - schedule: { - number: '1', - unit: 'm', - }, - proxy_url: '', - proxy_use_local_resolver: false, - 'service.name': '', - 'ssl.certificate': '', - 'ssl.certificate_authorities': '', - 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], - 'ssl.verification_mode': isTLSEnabled ? 'strict' : 'full', - 'ssl.key': '', - 'ssl.key_passphrase': '', - tags: Array.isArray(monitor.tags) ? monitor.tags : monitor.tags?.split(','), - timeout: '16', - type: 'tcp', - hosts: Array.isArray(monitor.hosts) ? monitor.hosts?.[0] : monitor.hosts, - 'url.port': null, - urls: '', - id: `${journeyId}-test-suite-default`, - hash: 'ekrjelkjrelkjre', - ipv6: true, - ipv4: true, - mode: 'any', - }); - } - } finally { - await Promise.all([ - successfulMonitors.map((monitor) => { - return deleteMonitor(monitor.id, tcpProjectMonitors.project); - }), - ]); - } - }); - - it('project monitors - handles icmp monitors', async () => { - const successfulMonitors = [icmpProjectMonitors.monitors[0], icmpProjectMonitors.monitors[1]]; - const kibanaVersion = await kibanaServer.version.get(); - - try { - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(icmpProjectMonitors) - ); - - expect(messages).to.have.length(3); - expect(messages[2].updatedMonitors).eql([]); - expect(messages[2].createdMonitors).eql(successfulMonitors.map((monitor) => monitor.id)); - expect(messages[2].failedMonitors).eql([ - { - id: icmpProjectMonitors.monitors[2].id, - details: `\`icmp\` project monitors must have exactly one value for field \`hosts\` in version \`${kibanaVersion}\`. Your monitor was not created or updated.`, - reason: 'Invalid Heartbeat configuration', - }, - { - id: icmpProjectMonitors.monitors[2].id, - details: `The following Heartbeat options are not supported for ${icmpProjectMonitors.monitors[0].type} project monitors in ${kibanaVersion}: unsupportedKey.nestedUnsupportedKey. You monitor was not created or updated.`, - reason: 'Unsupported Heartbeat option', - }, - ]); - - for (const monitor of successfulMonitors) { - const journeyId = monitor.id; - const createdMonitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${journeyId}` }) - .set('kbn-xsrf', 'true') - .expect(200); - - const decryptedCreatedMonitor = await supertest - .get(`${API_URLS.SYNTHETICS_MONITORS}/${createdMonitorsResponse.body.monitors[0].id}`) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(decryptedCreatedMonitor.body.attributes).to.eql({ - config_id: decryptedCreatedMonitor.body.id, - custom_heartbeat_id: `${journeyId}-test-suite-default`, - enabled: true, - alert: { - status: { - enabled: true, - }, - }, - form_monitor_type: 'icmp', - journey_id: journeyId, - locations: [ - { - geo: { - lat: 0, - lon: 0, - }, - id: 'localhost', - isServiceManaged: true, - label: 'Local Synthetics Service', - }, - { - geo: { - lat: '', - lon: '', - }, - id: testPolicyId, - isServiceManaged: false, - label: 'Test private location 0', - }, - ], - name: monitor.name, - namespace: 'default', - origin: 'project', - original_space: 'default', - project_id: 'test-suite', - revision: 1, - schedule: { - number: '1', - unit: 'm', - }, - 'service.name': '', - tags: Array.isArray(monitor.tags) ? monitor.tags : monitor.tags?.split(','), - timeout: '16', - type: 'icmp', - hosts: Array.isArray(monitor.hosts) ? monitor.hosts?.[0] : monitor.hosts, - wait: - monitor.wait?.slice(-1) === 's' - ? monitor.wait?.slice(0, -1) - : `${parseInt(monitor.wait?.slice(0, -1) || '1', 10) * 60}`, - id: `${journeyId}-test-suite-default`, - hash: 'ekrjelkjrelkjre', - ipv6: true, - ipv4: true, - mode: 'any', - }); - } - } finally { - await Promise.all([ - successfulMonitors.map((monitor) => { - return deleteMonitor(monitor.id, icmpProjectMonitors.project); - }), - ]); - } - }); - - it('project monitors - returns a list of successfully created monitors', async () => { - try { - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(projectMonitors) - ); - - expect(messages).to.have.length(2); - expect(messages[1].updatedMonitors).eql([]); - expect(messages[1].failedMonitors).eql([]); - expect(messages[1].createdMonitors).eql( - projectMonitors.monitors.map((monitor) => monitor.id) - ); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - returns error if the space does not exist', async () => { - const messages = await parseStreamApiResponse( - kibanaServerUrl + '/s/i_dont_exist' + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY, - JSON.stringify(projectMonitors) - ); - - expect(messages).to.have.length(2); - expect(messages[0]).to.equal( - "Unable to create monitors. Kibana space 'i_dont_exist' does not exist." - ); - expect(messages[1].failedMonitors).to.eql(projectMonitors.monitors.map((m) => m.id)); - }); - - it('project monitors - returns a list of successfully updated monitors', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(projectMonitors); - - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(projectMonitors) - ); - - expect(messages).to.have.length(2); - expect(messages[0]).eql(' 1 monitor found with no changes.'); - expect(messages[1].createdMonitors).eql([]); - expect(messages[1].failedMonitors).eql([]); - expect(messages[1].updatedMonitors).eql( - projectMonitors.monitors.map((monitor) => monitor.id) - ); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - does not increment monitor revision unless a change has been made', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(projectMonitors); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(projectMonitors); - - const updatedMonitorsResponse = await Promise.all( - projectMonitors.monitors.map((monitor) => { - return supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${monitor.id}` }) - .set('kbn-xsrf', 'true') - .expect(200); - }) - ); - - updatedMonitorsResponse.forEach((response) => { - expect(response.body.monitors[0].attributes.revision).eql(1); - }); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - increments monitor revision when a change has been made', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(projectMonitors); - - const editedMonitors = { - ...projectMonitors, - monitors: projectMonitors.monitors.map((monitor) => ({ - ...monitor, - content: 'changed content', - })), - }; - - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(editedMonitors) - ); - - const updatedMonitorsResponse = await Promise.all( - projectMonitors.monitors.map((monitor) => { - return supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${monitor.id}` }) - .set('kbn-xsrf', 'true') - .expect(200); - }) - ); - - updatedMonitorsResponse.forEach((response) => { - expect(response.body.monitors[0].attributes.revision).eql(2); - }); - expect(messages[0]).eql('1 monitor updated successfully. '); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - does not delete monitors when keep stale is true', async () => { - const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: testMonitors, - }); - - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(projectMonitors) - ); - - expect(messages).to.have.length(2); - expect(messages[0]).eql(' 1 monitor found with no changes.'); - expect(messages[1].createdMonitors).eql([]); - expect(messages[1].failedMonitors).eql([]); - expect(messages[1].deletedMonitors).eql([]); - expect(messages[1].updatedMonitors).eql([projectMonitors.monitors[0].id]); - expect(messages[1].staleMonitors).eql([secondMonitor.id]); - // does not delete the stale monitor - const getResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const { monitors } = getResponse.body; - - expect(monitors.length).eql(1); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - deletes monitors when keep stale is false', async () => { - const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - keep_stale: false, - monitors: testMonitors, - project: 'test-project-2', - }); - - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - project: 'test-project-2', - }) - ); - - expect(messages).to.have.length(3); - - // expect monitor to have been deleted - const getResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const { monitors } = getResponse.body; - expect(monitors[0]).eql(undefined); - expect(messages[0]).eql(` 1 monitor found with no changes.`); - expect(messages[1]).eql(`Monitor ${secondMonitor.id} deleted successfully`); - expect(messages[2].createdMonitors).eql([]); - expect(messages[2].failedMonitors).eql([]); - expect(messages[2].updatedMonitors).eql([projectMonitors.monitors[0].id]); - expect(messages[2].deletedMonitors).eql([secondMonitor.id]); - expect(messages[2].staleMonitors).eql([]); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - does not delete monitors from different suites when keep stale is false', async () => { - const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - const testprojectId = 'test-suite-2'; - try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: testMonitors, - }) - ); - - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - project: testprojectId, - }) - ); - - expect(messages).to.have.length(2); - expect(messages[1].createdMonitors).eql([projectMonitors.monitors[0].id]); - expect(messages[1].failedMonitors).eql([]); - expect(messages[1].deletedMonitors).eql([]); - expect(messages[1].updatedMonitors).eql([]); - expect(messages[1].staleMonitors).eql([]); - - // expect monitor not to have been deleted - const getResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const { monitors } = getResponse.body; - - expect(monitors.length).eql(1); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor(monitor.id, testprojectId); - }), - ]); - } - }); - - it('project monitors - does not delete a monitor from the same suite in a different space', async () => { - const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - const username = 'admin'; - const roleName = `synthetics_admin`; - const password = `${username}-password`; - const SPACE_ID = `test-space-${uuidv4()}`; - const SPACE_NAME = `test-space-name ${uuidv4()}`; - await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ ...projectMonitors, keep_stale: false, monitors: testMonitors }), - { - Authorization: - 'Basic ' + Buffer.from(`${username}:${password}`, 'binary').toString('base64'), - } - ); - - const spaceUrl = - kibanaServerUrl + `/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY}`; - - const messages = await parseStreamApiResponse( - spaceUrl, - JSON.stringify({ ...projectMonitors, keep_stale: false }), - { - Authorization: - 'Basic ' + Buffer.from(`${username}:${password}`, 'binary').toString('base64'), - } - ); - expect(messages).to.have.length(2); - expect(messages[1].createdMonitors).eql([projectMonitors.monitors[0].id]); - expect(messages[1].failedMonitors).eql([]); - expect(messages[1].deletedMonitors).eql([]); - expect(messages[1].updatedMonitors).eql([]); - expect(messages[1].staleMonitors).eql([]); - - const getResponse = await supertestWithoutAuth - .get(API_URLS.SYNTHETICS_MONITORS) - .auth(username, password) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - const { monitors } = getResponse.body; - expect(monitors.length).eql(1); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor( - monitor.id, - projectMonitors.project, - 'default', - username, - password - ); - }), - ]); - await deleteMonitor( - projectMonitors.monitors[0].id, - projectMonitors.project, - SPACE_ID, - username, - password - ); - await security.user.delete(username); - await security.role.delete(roleName); - } - }); - - it('project monitors - validates monitor type', async () => { - try { - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - monitors: [{ ...projectMonitors.monitors[0], schedule: '3m', tags: '' }], - }) - ); - - expect(messages).to.have.length(1); - expect(messages[0].updatedMonitors).eql([]); - expect(messages[0].failedMonitors).eql([ - { - details: 'Invalid value "3m" supplied to "schedule"', - id: projectMonitors.monitors[0].id, - payload: { - content: - 'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA', - filter: { - match: 'check if title is present', - }, - id: projectMonitors.monitors[0].id, - locations: ['localhost'], - name: 'check if title is present', - params: {}, - playwrightOptions: { - chromiumSandbox: false, - headless: true, - }, - schedule: '3m', - tags: '', - throttling: { - download: 5, - latency: 20, - upload: 3, - }, - type: 'browser', - hash: 'ekrjelkjrelkjre', - }, - reason: "Couldn't save or update monitor because of an invalid configuration.", - }, - ]); - expect(messages[0].createdMonitors).eql([]); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - saves space as data stream namespace', async () => { - const username = 'admin'; - const roleName = `synthetics_admin`; - const password = `${username}-password`; - const SPACE_ID = `test-space-${uuidv4()}`; - const SPACE_NAME = `test-space-name ${uuidv4()}`; - await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - await supertestWithoutAuth - .put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY}`) - .auth(username, password) - .set('kbn-xsrf', 'true') - .send(projectMonitors) - .expect(200); - // expect monitor not to have been deleted - const getResponse = await supertestWithoutAuth - .get(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`) - .auth(username, password) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - const { monitors } = getResponse.body; - expect(monitors.length).eql(1); - expect(monitors[0].attributes[ConfigKey.NAMESPACE]).eql(formatKibanaNamespace(SPACE_ID)); - } finally { - await deleteMonitor( - projectMonitors.monitors[0].id, - projectMonitors.project, - SPACE_ID, - username, - password - ); - await security.user.delete(username); - await security.role.delete(roleName); - } - }); - - it('project monitors - formats custom id appropriately', async () => { - const username = 'admin'; - const roleName = `synthetics_admin`; - const password = `${username}-password`; - const SPACE_ID = `test-space-${uuidv4()}`; - const SPACE_NAME = `test-space-name ${uuidv4()}`; - await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - await supertestWithoutAuth - .put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY}`) - .auth(username, password) - .set('kbn-xsrf', 'true') - .send(projectMonitors) - .expect(200); - // expect monitor not to have been deleted - const getResponse = await supertestWithoutAuth - .get(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`) - .auth(username, password) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - const { monitors } = getResponse.body; - expect(monitors.length).eql(1); - expect(monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]).eql( - `${projectMonitors.monitors[0].id}-${projectMonitors.project}-${SPACE_ID}` - ); - } finally { - await deleteMonitor( - projectMonitors.monitors[0].id, - projectMonitors.project, - SPACE_ID, - username, - password - ); - await security.user.delete(username); - await security.role.delete(roleName); - } - }); - - it('project monitors - is able to decrypt monitor when updated after hydration', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(projectMonitors); - - const response = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const { monitors } = response.body; - - // add urls and ports to mimic hydration - const updates = { - [ConfigKey.URLS]: 'https://modified-host.com', - [ConfigKey.PORT]: 443, - }; - - const modifiedMonitor = { ...monitors[0]?.attributes, ...updates }; - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS + '/' + monitors[0]?.id) - .set('kbn-xsrf', 'true') - .send(modifiedMonitor) - .expect(200); - - // update project monitor via push api - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify(projectMonitors) - ); - expect(messages).to.have.length(2); - expect(messages[0]).eql('1 monitor updated successfully. '); - expect(messages[1].updatedMonitors).eql([projectMonitors.monitors[0].id]); - - // ensure that monitor can still be decrypted - await supertest - .get(API_URLS.SYNTHETICS_MONITORS + '/' + monitors[0]?.id) - .set('kbn-xsrf', 'true') - .expect(200); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - is able to enable and disable monitors', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(projectMonitors); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { - ...projectMonitors.monitors[0], - enabled: false, - }, - ], - }) - .expect(200); - const response = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - const { monitors } = response.body; - expect(monitors[0].attributes.enabled).eql(false); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - - it('project monitors - returns a failed monitor when user defines a private location without fleet permissions', async () => { - const secondMonitor = { - ...projectMonitors.monitors[0], - id: 'test-id-2', - privateLocations: ['Test private location 0'], - }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - const username = 'admin'; - const roleName = 'uptime read only'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - const messages = await parseStreamApiResponse( - kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: testMonitors, - }), - { - Authorization: - 'Basic ' + Buffer.from(`${username}:${password}`, 'binary').toString('base64'), - } - ); - - expect(messages).to.have.length(3); - expect(messages[0]).to.eql('test-id-2: failed to create or update monitor'); - expect(messages[1]).to.eql(`1 monitor created successfully.`); - expect(messages[2]).to.eql({ - createdMonitors: [testMonitors[0].id], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [], - failedMonitors: [ - { - details: - 'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.', - id: 'test-id-2', - payload: { - content: - 'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA', - filter: { - match: 'check if title is present', - }, - id: 'test-id-2', - locations: ['localhost'], - name: 'check if title is present', - params: {}, - playwrightOptions: { - chromiumSandbox: false, - headless: true, - }, - privateLocations: ['Test private location 0'], - schedule: 10, - tags: [], - throttling: { - download: 5, - latency: 20, - upload: 3, - }, - hash: 'ekrjelkjrelkjre', - }, - reason: 'Failed to create or update monitor', - }, - ], - failedStaleMonitors: [], - }); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor( - monitor.id, - projectMonitors.project, - 'default', - username, - password - ); - }), - ]); - await security.user.delete(username); - await security.role.delete(roleName); - } - }); - - it('project monitors - returns a failed monitor when user tries to delete a monitor without fleet permissions', async () => { - const secondMonitor = { - ...projectMonitors.monitors[0], - id: 'test-id-2', - privateLocations: ['Test private location 0'], - }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - const username = 'test-username'; - const roleName = 'uptime read only'; - const password = `test-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - await parseStreamApiResponse( - kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: testMonitors, - }) - ); - - const messages = await parseStreamApiResponse( - kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: [], - }), - { - Authorization: - 'Basic ' + Buffer.from(`${username}:${password}`, 'binary').toString('base64'), - } - ); - - expect(messages).to.have.length(3); - expect( - messages.filter((msg) => msg === `Monitor ${testMonitors[1].id} could not be deleted`) - ).to.have.length(1); - expect( - messages.filter((msg) => msg === `Monitor ${testMonitors[0].id} deleted successfully`) - ).to.have.length(1); - expect(messages[2]).to.eql({ - createdMonitors: [], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [testMonitors[0].id], - failedMonitors: [], - failedStaleMonitors: [ - { - details: - 'Unable to delete Synthetics package policy for monitor check if title is present. Fleet write permissions are needed to use Synthetics private locations.', - id: 'test-id-2', - reason: 'Failed to delete stale monitor', - }, - ], - }); - - const messages2 = await parseStreamApiResponse( - kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: [], - }) - ); - - expect(messages2).to.have.length(2); - expect(messages2[0]).to.eql(`Monitor ${testMonitors[1].id} deleted successfully`); - expect(messages2[1]).to.eql({ - createdMonitors: [], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [testMonitors[1].id], - failedMonitors: [], - failedStaleMonitors: [], - }); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor( - monitor.id, - projectMonitors.project, - 'default', - username, - password - ); - }), - ]); - await security.user.delete(username); - await security.role.delete(roleName); - } - }); - - it('project monitors - returns a successful monitor when user defines a private location with fleet permissions', async () => { - const secondMonitor = { - ...projectMonitors.monitors[0], - id: 'test-id-2', - privateLocations: ['Test private location 0'], - }; - const testMonitors = [projectMonitors.monitors[0], secondMonitor]; - const username = 'admin'; - const roleName = 'uptime with fleet'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - fleetv2: ['all'], - fleet: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - const messages = await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: testMonitors, - }) - ); - expect(messages).to.have.length(2); - expect(messages).to.eql([ - `2 monitors created successfully.`, - { - createdMonitors: [testMonitors[0].id, 'test-id-2'], - updatedMonitors: [], - staleMonitors: [], - deletedMonitors: [], - failedMonitors: [], - failedStaleMonitors: [], - }, - ]); - } finally { - await Promise.all([ - testMonitors.map((monitor) => { - return deleteMonitor( - monitor.id, - projectMonitors.project, - 'default', - username, - password - ); - }), - ]); - await security.user.delete(username); - await security.role.delete(roleName); - } - }); - - it('creates integration policies for project monitors with private locations', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { ...projectMonitors.monitors[0], privateLocations: ['Test private location 0'] }, - ], - }); - - const monitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const apiResponsePolicy = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy = apiResponsePolicy.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - }-${testPolicyId}` - ); - expect(packagePolicy.name).eql( - `${projectMonitors.monitors[0].id}-${projectMonitors.project}-default-Test private location 0` - ); - expect(packagePolicy.policy_id).eql(testPolicyId); - - const configId = monitorsResponse.body.monitors[0].id; - const id = monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]; - - comparePolicies( - packagePolicy, - getTestProjectSyntheticsPolicy({ - inputs: {}, - name: 'check if title is present-Test private location 0', - id, - configId, - locationName: 'Test private location 0', - }) - ); - } finally { - await deleteMonitor(projectMonitors.monitors[0].id, projectMonitors.project); - - const packagesResponse = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - expect(packagesResponse.body.items.length).eql(0); - } - }); - - it('deletes integration policies for project monitors when private location is removed from the monitor - lightweight', async () => { - const monitorRequest = { - ...httpProjectMonitors, - monitors: [ - { ...httpProjectMonitors.monitors[1], privateLocations: ['Test private location 0'] }, - ], - }; - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(monitorRequest); - - const monitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${monitorRequest.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const apiResponsePolicy = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy = apiResponsePolicy.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - }-${testPolicyId}` - ); - - expect(packagePolicy.policy_id).eql(testPolicyId); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...monitorRequest, - monitors: [{ ...monitorRequest.monitors[0], privateLocations: [] }], - }); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy2 = apiResponsePolicy2.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - }-${testPolicyId}` - ); - - expect(packagePolicy2).eql(undefined); - } finally { - await deleteMonitor(projectMonitors.monitors[0].id, projectMonitors.project); - } - }); - - it('deletes integration policies for project monitors when private location is removed from the monitor', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { ...projectMonitors.monitors[0], privateLocations: ['Test private location 0'] }, - ], - }); - - const monitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const apiResponsePolicy = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy = apiResponsePolicy.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - }-${testPolicyId}` - ); - - expect(packagePolicy.policy_id).eql(testPolicyId); - - const configId = monitorsResponse.body.monitors[0].id; - const id = monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]; - - comparePolicies( - packagePolicy, - getTestProjectSyntheticsPolicy({ - inputs: {}, - name: 'check if title is present-Test private location 0', - id, - configId, - locationName: 'Test private location 0', - }) - ); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [{ ...projectMonitors.monitors[0], privateLocations: [] }], - }); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy2 = apiResponsePolicy2.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - }-${testPolicyId}` - ); - - expect(packagePolicy2).eql(undefined); - } finally { - await deleteMonitor(projectMonitors.monitors[0].id, projectMonitors.project); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - expect(apiResponsePolicy2.body.items.length).eql(0); - } - }); - - it('deletes integration policies when project monitors are deleted', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { ...projectMonitors.monitors[0], privateLocations: ['Test private location 0'] }, - ], - }) - .expect(200); - - const monitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const apiResponsePolicy = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy = apiResponsePolicy.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] + - '-' + - testPolicyId - ); - - expect(packagePolicy.policy_id).eql(testPolicyId); - - const configId = monitorsResponse.body.monitors[0].id; - const id = monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]; - - comparePolicies( - packagePolicy, - getTestProjectSyntheticsPolicy({ - inputs: {}, - name: 'check if title is present-Test private location 0', - id, - configId, - locationName: 'Test private location 0', - }) - ); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [], - keep_stale: false, - }); - - const monitorsResponse2 = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(monitorsResponse2.body.monitors.length).eql(0); - - await new Promise((resolve) => { - setTimeout(() => resolve(null), 3000); - }); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy2 = apiResponsePolicy2.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] + - '-' + - testPolicyId - ); - - expect(packagePolicy2).eql(undefined); - } finally { - await deleteMonitor(projectMonitors.monitors[0].id, projectMonitors.project); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - expect(apiResponsePolicy2.body.items.length).eql(0); - } - }); - - it('deletes integration policies when project monitors are deleted - lightweight', async () => { - const monitorRequest = { - ...httpProjectMonitors, - monitors: [ - { ...httpProjectMonitors.monitors[1], privateLocations: ['Test private location 0'] }, - ], - }; - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send(monitorRequest); - - const monitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${monitorRequest.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const apiResponsePolicy = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy = apiResponsePolicy.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - }-${testPolicyId}` - ); - - expect(packagePolicy.policy_id).eql(testPolicyId); - - const configId = monitorsResponse.body.monitors[0].id; - const id = monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]; - - const httpInput = packagePolicy.inputs.find( - (input: any) => input.type === 'synthetics/http' - ); - expect(httpInput).to.eql({ - type: 'synthetics/http', - policy_template: 'synthetics', - enabled: true, - streams: [ - { - enabled: true, - data_stream: { - elasticsearch: { - privileges: { - indices: ['auto_configure', 'create_doc', 'read'], - }, - }, - type: 'synthetics', - dataset: 'http', - }, - vars: { - __ui: { value: '{"is_tls_enabled":true}', type: 'yaml' }, - enabled: { value: false, type: 'bool' }, - type: { value: 'http', type: 'text' }, - name: { value: 'My Monitor 3', type: 'text' }, - schedule: { value: '"@every 60m"', type: 'text' }, - urls: { value: 'http://localhost:9200', type: 'text' }, - 'service.name': { value: '', type: 'text' }, - timeout: { value: '80s', type: 'text' }, - max_redirects: { value: '0', type: 'integer' }, - proxy_url: { value: '', type: 'text' }, - proxy_headers: { value: null, type: 'yaml' }, - tags: { value: '["tag2","tag2"]', type: 'yaml' }, - username: { value: '', type: 'text' }, - password: { value: '', type: 'password' }, - 'response.include_headers': { value: false, type: 'bool' }, - 'response.include_body': { value: 'always', type: 'text' }, - 'response.include_body_max_bytes': { value: '900', type: 'text' }, - 'check.request.method': { value: 'POST', type: 'text' }, - 'check.request.headers': { - value: '{"Content-Type":"application/x-www-form-urlencoded"}', - type: 'yaml', - }, - 'check.request.body': { value: null, type: 'yaml' }, - 'check.response.status': { value: '["200"]', type: 'yaml' }, - 'check.response.headers': { value: null, type: 'yaml' }, - 'check.response.body.positive': { value: '["Saved","saved"]', type: 'yaml' }, - 'check.response.body.negative': { value: null, type: 'yaml' }, - 'check.response.json': { - value: '[{"description":"check status","expression":"foo.bar == \\"myValue\\""}]', - type: 'yaml', - }, - 'ssl.certificate_authorities': { value: null, type: 'yaml' }, - 'ssl.certificate': { value: null, type: 'yaml' }, - 'ssl.key': { value: null, type: 'yaml' }, - 'ssl.key_passphrase': { value: null, type: 'text' }, - 'ssl.verification_mode': { value: 'strict', type: 'text' }, - 'ssl.supported_protocols': { - value: '["TLSv1.1","TLSv1.2","TLSv1.3"]', - type: 'yaml', - }, - location_name: { value: 'Test private location 0', type: 'text' }, - id: { - value: id, - type: 'text', - }, - config_id: { value: configId, type: 'text' }, - run_once: { value: false, type: 'bool' }, - origin: { value: 'project', type: 'text' }, - 'monitor.project.id': { - type: 'text', - value: 'test-suite', - }, - 'monitor.project.name': { - type: 'text', - value: 'test-suite', - }, - ipv4: { type: 'bool', value: true }, - ipv6: { type: 'bool', value: true }, - mode: { type: 'text', value: 'any' }, - }, - id: `synthetics/http-http-${id}-${testPolicyId}`, - compiled_stream: { - __ui: { is_tls_enabled: true }, - type: 'http', - name: 'My Monitor 3', - id, - origin: 'project', - enabled: false, - urls: 'http://localhost:9200', - schedule: '@every 60m', - timeout: '80s', - max_redirects: 0, - tags: ['tag2', 'tag2'], - 'response.include_headers': false, - 'response.include_body': 'always', - 'check.request.method': 'POST', - 'check.request.headers': { 'Content-Type': 'application/x-www-form-urlencoded' }, - 'check.response.status': ['200'], - 'check.response.body.positive': ['Saved', 'saved'], - 'ssl.verification_mode': 'strict', - 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], - 'run_from.geo.name': 'Test private location 0', - 'run_from.id': 'Test private location 0', - 'check.response.json': [ - { - description: 'check status', - expression: 'foo.bar == "myValue"', - }, - ], - ipv4: true, - ipv6: true, - mode: 'any', - processors: [ - { - add_fields: { - target: '', - fields: { - 'monitor.fleet_managed': true, - config_id: configId, - 'monitor.project.id': 'test-suite', - 'monitor.project.name': 'test-suite', - }, - }, - }, - ], - }, - }, - ], - }); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...monitorRequest, - monitors: [], - }); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const packagePolicy2 = apiResponsePolicy2.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === - `${ - monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] - } - ${testPolicyId}` - ); - - expect(packagePolicy2).eql(undefined); - } finally { - await deleteMonitor(httpProjectMonitors.monitors[1].id, httpProjectMonitors.project); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - expect(apiResponsePolicy2.body.items.length).eql(0); - } - }); - - it('handles updating package policies when project monitors are updated', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { - ...projectMonitors.monitors[0], - privateLocations: ['Test private location 0'], - }, - ], - }); - - const monitorsResponse = await supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ - filter: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`, - }) - .set('kbn-xsrf', 'true') - .expect(200); - - const apiResponsePolicy = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const configId = monitorsResponse.body.monitors[0].id; - const id = monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]; - const policyId = `${id}-${testPolicyId}`; - - const packagePolicy = apiResponsePolicy.body.items.find( - (pkgPolicy: PackagePolicy) => pkgPolicy.id === policyId - ); - - expect(packagePolicy.policy_id).eql(testPolicyId); - - comparePolicies( - packagePolicy, - getTestProjectSyntheticsPolicy({ - inputs: {}, - name: 'check if title is present-Test private location 0', - id, - configId, - locationName: 'Test private location 0', - }) - ); - - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { - ...projectMonitors.monitors[0], - privateLocations: ['Test private location 0'], - enabled: false, - }, - ], - }); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - - const configId2 = monitorsResponse.body.monitors[0].id; - const id2 = monitorsResponse.body.monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]; - const policyId2 = `${id}-${testPolicyId}`; - - const packagePolicy2 = apiResponsePolicy2.body.items.find( - (pkgPolicy: PackagePolicy) => pkgPolicy.id === policyId2 - ); - - comparePolicies( - packagePolicy2, - getTestProjectSyntheticsPolicy({ - inputs: { enabled: { value: false, type: 'bool' } }, - name: 'check if title is present-Test private location 0', - id: id2, - configId: configId2, - locationName: 'Test private location 0', - }) - ); - } finally { - await deleteMonitor(projectMonitors.monitors[0].id, projectMonitors.project); - - const apiResponsePolicy2 = await supertest.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - expect(apiResponsePolicy2.body.items.length).eql(0); - } - }); - - it('handles location formatting for both private and public locations', async () => { - try { - await supertest - .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY) - .set('kbn-xsrf', 'true') - .send({ - ...projectMonitors, - monitors: [ - { ...projectMonitors.monitors[0], privateLocations: ['Test private location 0'] }, - ], - }); - - const updatedMonitorsResponse = await Promise.all( - projectMonitors.monitors.map((monitor) => { - return supertest - .get(API_URLS.SYNTHETICS_MONITORS) - .query({ filter: `${syntheticsMonitorType}.attributes.journey_id: ${monitor.id}` }) - .set('kbn-xsrf', 'true') - .expect(200); - }) - ); - - updatedMonitorsResponse.forEach((response) => { - expect(response.body.monitors[0].attributes.locations).eql([ - { - id: 'localhost', - label: 'Local Synthetics Service', - geo: { lat: 0, lon: 0 }, - isServiceManaged: true, - }, - { - label: 'Test private location 0', - isServiceManaged: false, - id: testPolicyId, - geo: { - lat: '', - lon: '', - }, - }, - ]); - }); - } finally { - await Promise.all([ - projectMonitors.monitors.map((monitor) => { - return deleteMonitor(monitor.id, projectMonitors.project); - }), - ]); - } - }); - }); -} - -/** - * Borrowed from AIOPS test code: https://github.com/elastic/kibana/blob/23a7ac2c2e2b1f64daa17b914e86989b1fde750c/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts - * Receives a stream and parses the messages until the stream closes. - */ -async function* parseStream(stream: NodeJS.ReadableStream) { - let partial = ''; - - try { - for await (const value of stream) { - const full = `${partial}${value}`; - const parts = full.split('\n'); - const last = parts.pop(); - - partial = last ?? ''; - - const event = parts.map((p) => JSON.parse(p)); - - for (const events of event) { - yield events; - } - } - } catch (error) { - yield { type: 'error', payload: error.toString() }; - } -} - -/** - * Helper function to process the results of the module's stream parsing helper function. - */ -async function getMessages(stream: NodeJS.ReadableStream | null) { - if (stream === null) return []; - const data: any[] = []; - for await (const action of parseStream(stream)) { - data.push(action); - } - return data; -} - -/** - * This type is intended to highlight any break between shared parameter contracts defined in - * the module's streaming endpoint helper functions. - */ -type StreamApiFunction = ( - url: string, - body?: BodyInit, - extraHeaders?: HeadersInit, - method?: string -) => T; - -/** - * This helps the test file have DRY code when it comes to calling - * the same streaming endpoint over and over by defining some selective defaults. - */ -export const parseStreamApiResponse: StreamApiFunction> = async ( - url: string, - body?: BodyInit, - extraHeaders?: HeadersInit, - method = 'PUT' -) => { - const streamResponse = await callStreamApi(url, body, extraHeaders, method); - return getMessages(streamResponse.body); -}; - -/** - * This helps the test file have DRY code when it comes to calling - * the same streaming endpoint over and over by defining some selective defaults. - */ -const callStreamApi: StreamApiFunction> = async ( - url: string, - body?: BodyInit, - extraHeaders?: HeadersInit, - method = 'PUT' -) => { - return fetch(url, { - method, - headers: { - 'Content-Type': 'application/json', - 'kbn-xsrf': 'stream', - ...extraHeaders, - }, - body, - }); -}; diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts index 13bcab980d248d..d4fe302b956a20 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts @@ -5,7 +5,6 @@ * 2.0. */ import { v4 as uuidv4 } from 'uuid'; -import { format as formatUrl } from 'url'; import { ConfigKey, ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types'; import { INSUFFICIENT_FLEET_PERMISSIONS } from '@kbn/synthetics-plugin/server/synthetics_service/project_monitor/project_monitor_formatter'; import { REQUEST_TOO_LARGE } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/delete_monitor_project'; @@ -16,19 +15,15 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; -import { parseStreamApiResponse } from './add_monitor_project_legacy'; export default function ({ getService }: FtrProviderContext) { describe('DeleteProjectMonitors', function () { this.tags('skipCloud'); const supertest = getService('supertest'); - const config = getService('config'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const security = getService('security'); - const kibanaServerUrl = formatUrl(config.get('servers.kibana')); const kibanaServer = getService('kibanaServer'); - const projectMonitorEndpoint = kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY; let projectMonitors: ProjectMonitorsRequest; @@ -70,16 +65,20 @@ export default function ({ getService }: FtrProviderContext) { name: `test-name-${i}`, }); } + const monitorsToDelete = monitors.map((monitor) => monitor.id); try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitors.slice(0, 250) }) + .expect(200); + + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitors.slice(250, 251) }) + .expect(200); const savedObjectsResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS) @@ -89,7 +88,6 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true'); const { total } = savedObjectsResponse.body; expect(total).to.eql(251); - const monitorsToDelete = monitors.map((monitor) => monitor.id); const response = await supertest .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) @@ -99,15 +97,16 @@ export default function ({ getService }: FtrProviderContext) { const { message } = response.body; expect(message).to.eql(REQUEST_TOO_LARGE); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 251) }) + .expect(200); } }); @@ -123,14 +122,11 @@ export default function ({ getService }: FtrProviderContext) { const project = 'test-brower-suite'; try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors }) + .expect(200); const savedObjectsResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS) @@ -161,15 +157,12 @@ export default function ({ getService }: FtrProviderContext) { const { total: totalAfterDeletion } = responseAfterDeletion.body; expect(totalAfterDeletion).to.eql(1); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); } }); @@ -179,23 +172,17 @@ export default function ({ getService }: FtrProviderContext) { const secondProject = 'second-project'; try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors }) + .expect(200); - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project: secondProject, - monitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', secondProject)) + .set('kbn-xsrf', 'true') + .send({ monitors }) + .expect(200); const savedObjectsResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS) @@ -244,24 +231,20 @@ export default function ({ getService }: FtrProviderContext) { expect(totalAfterDeletion).to.eql(0); expect(secondProjectTotalAfterDeletion).to.eql(monitors.length); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project: secondProject, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); + await supertest + .delete( + API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', secondProject) + ) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); } }); @@ -270,27 +253,25 @@ export default function ({ getService }: FtrProviderContext) { const project = 'test-brower-suite'; const SPACE_ID = `test-space-${uuidv4()}`; const SPACE_NAME = `test-space-name ${uuidv4()}`; - const secondSpaceProjectMonitorApiRoute = `${kibanaServerUrl}/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY}`; await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors }) + .expect(200); - await parseStreamApiResponse( - secondSpaceProjectMonitorApiRoute, - JSON.stringify({ - ...projectMonitors, - project, - monitors, - }) - ); + await supertest + .put( + `/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace( + '{projectName}', + project + )}` + ) + .set('kbn-xsrf', 'true') + .send({ monitors }) + .expect(200); const savedObjectsResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS) @@ -345,24 +326,24 @@ export default function ({ getService }: FtrProviderContext) { expect(totalAfterDeletion).to.eql(monitors.length); expect(secondProjectTotalAfterDeletion).to.eql(0); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); - await parseStreamApiResponse( - secondSpaceProjectMonitorApiRoute, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete( + `/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace( + '{projectName}', + project + )}` + ) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); } }); @@ -373,14 +354,11 @@ export default function ({ getService }: FtrProviderContext) { const project = 'test-brower-suite'; try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors }) + .expect(200); const savedObjectsResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS) @@ -436,15 +414,13 @@ export default function ({ getService }: FtrProviderContext) { ); expect(packagePolicy2).to.be(undefined); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); } }); @@ -477,15 +453,11 @@ export default function ({ getService }: FtrProviderContext) { full_name: 'a kibana user', }); - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: testMonitors, - }) - ); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: testMonitors }) + .expect(200); const savedObjectsResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS) @@ -507,15 +479,11 @@ export default function ({ getService }: FtrProviderContext) { .expect(403); expect(message).to.eql(INSUFFICIENT_FLEET_PERMISSIONS); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete }) + .expect(200); await security.user.delete(username); await security.role.delete(roleName); } diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts index beefd6c5618292..f683e04cea46ba 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts @@ -6,7 +6,6 @@ */ import { v4 as uuidv4 } from 'uuid'; import type SuperTest from 'supertest'; -import { format as formatUrl } from 'url'; import { LegacyProjectMonitorsRequest, ProjectMonitor, @@ -17,16 +16,12 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; -import { parseStreamApiResponse } from './add_monitor_project_legacy'; export default function ({ getService }: FtrProviderContext) { describe('GetProjectMonitors', function () { this.tags('skipCloud'); const supertest = getService('supertest'); - const config = getService('config'); - const kibanaServerUrl = formatUrl(config.get('servers.kibana')); - const projectMonitorEndpoint = kibanaServerUrl + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY; let projectMonitors: LegacyProjectMonitorsRequest; let httpProjectMonitors: LegacyProjectMonitorsRequest; @@ -76,14 +71,28 @@ export default function ({ getService }: FtrProviderContext) { } try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(0, 250), }) - ); + .expect(200); + + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(250, 500), + }) + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(500, 600), + }) + .expect(200); const firstPageResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) @@ -108,15 +117,23 @@ export default function ({ getService }: FtrProviderContext) { expect(secondPageMonitors.length).to.eql(100); checkFields([...firstPageMonitors, ...secondPageMonitors], monitors); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 500) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(500, 600) }) + .expect(200); } }); @@ -132,14 +149,28 @@ export default function ({ getService }: FtrProviderContext) { } try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(0, 250), }) - ); + .expect(200); + + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(250, 500), + }) + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(500, 600), + }) + .expect(200); const firstPageResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) @@ -168,15 +199,23 @@ export default function ({ getService }: FtrProviderContext) { expect(secondPageProjectMonitors.length).to.eql(100); checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 500) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(500, 600) }) + .expect(200); } }); @@ -192,14 +231,28 @@ export default function ({ getService }: FtrProviderContext) { } try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(0, 250), + }) + .expect(200); + + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(250, 500), }) - ); + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(500, 600), + }) + .expect(200); const firstPageResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) @@ -228,15 +281,23 @@ export default function ({ getService }: FtrProviderContext) { expect(secondPageProjectMonitors.length).to.eql(100); checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 500) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(500, 600) }) + .expect(200); } }); @@ -252,15 +313,28 @@ export default function ({ getService }: FtrProviderContext) { } try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - monitors, + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(0, 250), }) - ); + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(250, 500), + }) + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(500, 600), + }) + .expect(200); const firstPageResponse = await supertest .get(API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) .set('kbn-xsrf', 'true') @@ -289,15 +363,23 @@ export default function ({ getService }: FtrProviderContext) { checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 500) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(500, 600) }) + .expect(200); } }); @@ -313,15 +395,28 @@ export default function ({ getService }: FtrProviderContext) { } try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project: projectName, - keep_stale: false, - monitors, + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', projectName)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(0, 250), }) - ); + .expect(200); + + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', projectName)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(250, 500), + }) + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', projectName)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(500, 600), + }) + .expect(200); const firstPageResponse = await supertest .get( @@ -355,20 +450,44 @@ export default function ({ getService }: FtrProviderContext) { checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - project: projectName, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete( + API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace( + '{projectName}', + encodeURI(projectName) + ) + ) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete( + API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace( + '{projectName}', + encodeURI(projectName) + ) + ) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 500) }) + .expect(200); + await supertest + .delete( + API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace( + '{projectName}', + encodeURI(projectName) + ) + ) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(500, 600) }) + .expect(200); } }); it('project monitors - handles per_page parameter', async () => { const monitors = []; + const project = 'test-suite'; const perPage = 250; for (let i = 0; i < 600; i++) { monitors.push({ @@ -379,20 +498,36 @@ export default function ({ getService }: FtrProviderContext) { } try { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - monitors, + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(0, 250), + }) + .expect(200); + + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(250, 500), }) - ); + .expect(200); + await supertest + .put(API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ + monitors: monitors.slice(500, 600), + }) + .expect(200); + let count = Number.MAX_VALUE; let afterId; const fullResponse: ProjectMonitorMetaData[] = []; let page = 1; while (count >= 250) { const response: SuperTest.Response = await supertest - .get(API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', 'test-suite')) + .get(API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) .set('kbn-xsrf', 'true') .query({ per_page: perPage, @@ -417,14 +552,23 @@ export default function ({ getService }: FtrProviderContext) { // expect(fullResponse.length).to.eql(600); // checkFields(fullResponse, monitors); } finally { - await parseStreamApiResponse( - projectMonitorEndpoint, - JSON.stringify({ - ...projectMonitors, - keep_stale: false, - monitors: [], - }) - ); + const monitorsToDelete = monitors.map((monitor) => monitor.id); + + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(0, 250) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(250, 500) }) + .expect(200); + await supertest + .delete(API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)) + .set('kbn-xsrf', 'true') + .send({ monitors: monitorsToDelete.slice(500, 600) }) + .expect(200); } }); }); diff --git a/x-pack/test/api_integration/apis/synthetics/index.ts b/x-pack/test/api_integration/apis/synthetics/index.ts index 29603ce3481aea..19720b43fcd3cb 100644 --- a/x-pack/test/api_integration/apis/synthetics/index.ts +++ b/x-pack/test/api_integration/apis/synthetics/index.ts @@ -20,7 +20,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get_monitor')); loadTestFile(require.resolve('./get_monitor_overview')); loadTestFile(require.resolve('./add_monitor')); - loadTestFile(require.resolve('./add_monitor_project_legacy')); loadTestFile(require.resolve('./add_monitor_project')); loadTestFile(require.resolve('./get_monitor_project')); loadTestFile(require.resolve('./add_monitor_private_location')); @@ -29,5 +28,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./delete_monitor_project')); loadTestFile(require.resolve('./synthetics_enablement')); loadTestFile(require.resolve('./sync_global_params')); + loadTestFile(require.resolve('./add_edit_params')); }); } diff --git a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts index f5ff56d2ee506f..3a44de5cbcf02e 100644 --- a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts +++ b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts @@ -7,7 +7,7 @@ import { ConfigKey, HTTPFields, - SyntheticsParam, + SyntheticsParamSO, } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS, SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { omit } from 'lodash'; @@ -158,7 +158,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.status).eql(200); - apiResponse.body.data.forEach(({ attributes }: SavedObject) => { + apiResponse.body.data.forEach(({ attributes }: SavedObject) => { params[attributes.key] = attributes.value; }); });