diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/filters/filters.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/filters/filters.ts index aab30d16226df..cc2a4cc758cc8 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/filters/filters.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/routes/filters/filters.ts @@ -10,6 +10,7 @@ import { legacySyntheticsMonitorTypeSingle, syntheticsMonitorAttributes, syntheticsMonitorSavedObjectType, + legacyMonitorAttributes, } from '../../../common/types/saved_objects'; import { ConfigKey, MonitorFiltersResult } from '../../../common/runtime_types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -20,23 +21,29 @@ type Buckets = Array<{ }>; interface AggsResponse { - monitorTypes: { - buckets: Buckets; - }; - locations: { - buckets: Buckets; - }; - tags: { - buckets: Buckets; - }; - projects: { - buckets: Buckets; - }; - schedules: { - buckets: Buckets; - }; + monitorTypes?: { buckets: Buckets }; + monitorTypesLegacy?: { buckets: Buckets }; + locations?: { buckets: Buckets }; + locationsLegacy?: { buckets: Buckets }; + tags?: { buckets: Buckets }; + tagsLegacy?: { buckets: Buckets }; + projects?: { buckets: Buckets }; + projectsLegacy?: { buckets: Buckets }; + schedules?: { buckets: Buckets }; + schedulesLegacy?: { buckets: Buckets }; } +const mergeBuckets = (...bucketSets: Array) => { + const map = new Map(); + for (const buckets of bucketSets) { + buckets?.forEach(({ key, doc_count: docCount }) => { + const k = String(key); + map.set(k, (map.get(k) ?? 0) + docCount); + }); + } + return Array.from(map.entries()).map(([label, count]) => ({ label, count })); +}; + export const getSyntheticsFilters: SyntheticsRestApiRouteFactory = () => ({ method: 'GET', path: SYNTHETICS_API_URLS.FILTERS, @@ -54,36 +61,29 @@ export const getSyntheticsFilters: SyntheticsRestApiRouteFactory ({ - label: key, - count, - })) ?? [], - tags: - tags?.buckets?.map(({ key, doc_count: count }) => ({ - label: key, - count, - })) ?? [], - locations: - locations?.buckets?.map(({ key, doc_count: count }) => ({ - label: key, - count, - })) ?? [], - projects: - projects?.buckets - ?.filter(({ key }) => key) - .map(({ key, doc_count: count }) => ({ - label: key, - count, - })) ?? [], - schedules: - schedules?.buckets?.map(({ key, doc_count: count }) => ({ - label: String(key), - count, - })) ?? [], + monitorTypes: mergeBuckets(monitorTypes?.buckets, monitorTypesLegacy?.buckets), + tags: mergeBuckets(tags?.buckets, tagsLegacy?.buckets), + locations: mergeBuckets(locations?.buckets, locationsLegacy?.buckets), + projects: mergeBuckets(projects?.buckets, projectsLegacy?.buckets).filter( + ({ label }) => label + ), + schedules: mergeBuckets(schedules?.buckets, schedulesLegacy?.buckets).map( + ({ label, count }) => ({ label: String(label), count }) + ), }; }, }); @@ -95,28 +95,58 @@ const aggs = { size: 10000, }, }, + monitorTypesLegacy: { + terms: { + field: `${legacyMonitorAttributes}.${ConfigKey.MONITOR_TYPE}.keyword`, + size: 10000, + }, + }, tags: { terms: { field: `${syntheticsMonitorAttributes}.${ConfigKey.TAGS}`, size: 10000, }, }, + tagsLegacy: { + terms: { + field: `${legacyMonitorAttributes}.${ConfigKey.TAGS}`, + size: 10000, + }, + }, locations: { terms: { field: `${syntheticsMonitorAttributes}.${ConfigKey.LOCATIONS}.id`, size: 10000, }, }, + locationsLegacy: { + terms: { + field: `${legacyMonitorAttributes}.${ConfigKey.LOCATIONS}.id`, + size: 10000, + }, + }, projects: { terms: { field: `${syntheticsMonitorAttributes}.${ConfigKey.PROJECT_ID}`, size: 10000, }, }, + projectsLegacy: { + terms: { + field: `${legacyMonitorAttributes}.${ConfigKey.PROJECT_ID}`, + size: 10000, + }, + }, schedules: { terms: { field: `${syntheticsMonitorAttributes}.${ConfigKey.SCHEDULE}.number`, size: 10000, }, }, + schedulesLegacy: { + terms: { + field: `${legacyMonitorAttributes}.${ConfigKey.SCHEDULE}.number`, + size: 10000, + }, + }, }; diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/synthetics/get_filters.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/synthetics/get_filters.ts index 6c028eed8cf20..ab14b92adbb75 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/synthetics/get_filters.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/synthetics/get_filters.ts @@ -8,10 +8,14 @@ import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { RoleCredentials } from '@kbn/ftr-common-functional-services'; import expect from '@kbn/expect'; -import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types'; -import { syntheticsMonitorSavedObjectType } from '@kbn/synthetics-plugin/common/types/saved_objects'; -import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; +import type { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types'; +import { + legacySyntheticsMonitorTypeSingle, + syntheticsMonitorSavedObjectType, +} from '@kbn/synthetics-plugin/common/types/saved_objects'; +import type { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; import { PrivateLocationTestService } from '../../services/synthetics_private_location'; +import { addMonitorAPIHelper } from './create_monitor'; export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { describe('getMonitorFilters', function () { @@ -25,15 +29,23 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { let privateLocation: PrivateLocation; after(async () => { - await kibanaServer.savedObjects.clean({ types: [syntheticsMonitorSavedObjectType] }); + await kibanaServer.savedObjects.clean({ + types: [syntheticsMonitorSavedObjectType, legacySyntheticsMonitorTypeSingle], + }); }); before(async () => { - await kibanaServer.savedObjects.clean({ types: [syntheticsMonitorSavedObjectType] }); + await kibanaServer.savedObjects.clean({ + types: [syntheticsMonitorSavedObjectType, legacySyntheticsMonitorTypeSingle], + }); editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor'); privateLocation = await privateLocationTestService.addTestPrivateLocation(); }); + const addMonitor = async (monitor: any, type?: string) => { + return addMonitorAPIHelper(supertest, monitor, 200, editorUser, samlAuth, false, type); + }; + it('get list of filters', async () => { const apiResponse = await supertest .get(SYNTHETICS_API_URLS.FILTERS) @@ -59,12 +71,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { locations: [privateLocation], }; - await supertest - .post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS) - .set(editorUser.apiKeyHeader) - .set(samlAuth.getInternalRequestHeader()) - .send(newMonitor) - .expect(200); + await addMonitor(newMonitor); const apiResponse = await supertest .get(SYNTHETICS_API_URLS.FILTERS) @@ -83,5 +90,83 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { schedules: [{ label: '3', count: 1 }], }); }); + + it('get list of filters for legacy saved object type monitor', async () => { + // Create a legacy monitor directly via savedObjectsClient + + // Use the internal savedObjectsClient to create a legacy monitor + await addMonitor( + { + name: 'Legacy Monitor', + type: 'icmp', + host: 'https://legacy.elastic.co', + tags: ['legacy', 'synthetics'], + locations: [privateLocation], + }, + legacySyntheticsMonitorTypeSingle + ); + + const apiResponse = await supertest + .get(SYNTHETICS_API_URLS.FILTERS) + .set(editorUser.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .expect(200); + + expect(apiResponse.body.monitorTypes).to.eql([ + { label: 'http', count: 1 }, + { label: 'icmp', count: 1 }, + ]); + expect(apiResponse.body.tags).to.eql([ + { label: 'apm', count: 1 }, + { label: 'synthetics', count: 2 }, + { label: 'legacy', count: 1 }, + ]); + expect(apiResponse.body.locations).to.eql([{ label: privateLocation.id, count: 2 }]); + expect(apiResponse.body.schedules).to.eql([{ label: '3', count: 2 }]); + }); + + it('get list of filters with both legacy and modern monitors', async () => { + // Create a modern monitor + const modernMonitor = { + name: 'Modern Monitor', + type: 'http', + urls: 'https://modern.elastic.co', + tags: ['multi-space', 'synthetics'], + locations: [privateLocation], + }; + + await addMonitor(modernMonitor); + + await addMonitor( + { + name: 'Legacy Monitor 3', + type: 'icmp', + host: 'https://legacy2.elastic.co', + tags: ['legacy2', 'synthetics'], + locations: [privateLocation], + }, + legacySyntheticsMonitorTypeSingle + ); + + const apiResponse = await supertest + .get(SYNTHETICS_API_URLS.FILTERS) + .set(editorUser.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .expect(200); + + expect(apiResponse.body.monitorTypes).to.eql([ + { label: 'http', count: 2 }, + { label: 'icmp', count: 2 }, + ]); + expect(apiResponse.body.tags).to.eql([ + { label: 'synthetics', count: 4 }, + { label: 'apm', count: 1 }, + { label: 'multi-space', count: 1 }, + { label: 'legacy', count: 1 }, + { label: 'legacy2', count: 1 }, + ]); + expect(apiResponse.body.locations).to.eql([{ label: privateLocation.id, count: 4 }]); + expect(apiResponse.body.schedules).to.eql([{ label: '3', count: 4 }]); + }); }); }