[Synthetics] Use msearch for multiple queries !! (#192065)

## Summary

Use msearch for multiple queries  for overview status !!

Also refactored to create separate query for status alert rule instead
of reusing !!
This commit is contained in:
Shahzad 2024-09-05 11:36:24 +02:00 committed by GitHub
parent 69665cecd0
commit c07e946e0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 783 additions and 502 deletions

View file

@ -0,0 +1,218 @@
/*
* 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 pMap from 'p-map';
import times from 'lodash/times';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { cloneDeep, intersection } from 'lodash';
import { createEsParams, SyntheticsEsClient } from '../../lib';
import {
OverviewPendingStatusMetaData,
OverviewPing,
OverviewStatus,
OverviewStatusMetaData,
} from '../../../common/runtime_types';
import { FINAL_SUMMARY_FILTER } from '../../../common/constants/client_defaults';
const DEFAULT_MAX_ES_BUCKET_SIZE = 10000;
const fields = [
'@timestamp',
'summary',
'monitor',
'observer',
'config_id',
'error',
'agent',
'url',
'state',
'tags',
];
export async function queryMonitorStatusForAlert(
esClient: SyntheticsEsClient,
monitorLocationIds: string[],
range: { from: string; to: string },
monitorQueryIds: string[],
monitorLocationsMap: Record<string, string[]>,
monitorQueryIdToConfigIdMap: Record<string, string>
): Promise<
Omit<
OverviewStatus,
| 'disabledCount'
| 'allMonitorsCount'
| 'disabledMonitorsCount'
| 'projectMonitorsCount'
| 'disabledMonitorQueryIds'
| 'allIds'
>
> {
const idSize = Math.trunc(DEFAULT_MAX_ES_BUCKET_SIZE / monitorLocationIds.length || 1);
const pageCount = Math.ceil(monitorQueryIds.length / idSize);
let up = 0;
let down = 0;
const upConfigs: Record<string, OverviewStatusMetaData> = {};
const downConfigs: Record<string, OverviewStatusMetaData> = {};
const monitorsWithoutData = new Map(Object.entries(cloneDeep(monitorLocationsMap)));
const pendingConfigs: Record<string, OverviewPendingStatusMetaData> = {};
await pMap(
times(pageCount),
async (i) => {
const idsToQuery = (monitorQueryIds as string[]).slice(i * idSize, i * idSize + idSize);
const params = createEsParams({
body: {
size: 0,
query: {
bool: {
filter: [
FINAL_SUMMARY_FILTER,
{
range: {
'@timestamp': {
gte: range.from,
lte: range.to,
},
},
},
{
terms: {
'monitor.id': idsToQuery,
},
},
] as QueryDslQueryContainer[],
},
},
aggs: {
id: {
terms: {
field: 'monitor.id',
size: idSize,
},
aggs: {
location: {
terms: {
field: 'observer.name',
size: monitorLocationIds.length || 100,
},
aggs: {
status: {
top_hits: {
size: 1,
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
_source: {
includes: fields,
},
},
},
},
},
},
},
},
},
});
if (monitorLocationIds.length > 0) {
params.body.query.bool.filter.push({
terms: {
'observer.name': monitorLocationIds,
},
});
}
const { body: result } = await esClient.search<OverviewPing, typeof params>(
params,
'getCurrentStatusOverview' + i
);
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
const locationSummaries = location.buckets.map(({ status, key: locationName }) => {
const ping = status.hits.hits[0]._source;
return { location: locationName, ping };
});
// discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are
// in monitorLocationsMap but not in listOfLocations
const monLocations = monitorLocationsMap?.[queryId];
const monQueriedLocations = intersection(monLocations, monitorLocationIds);
monQueriedLocations?.forEach((monLocation) => {
const locationSummary = locationSummaries.find(
(summary) => summary.location === monLocation
);
if (locationSummary) {
const { ping } = locationSummary;
const downCount = ping.summary?.down ?? 0;
const upCount = ping.summary?.up ?? 0;
const configId = ping.config_id;
const monitorQueryId = ping.monitor.id;
const meta = {
ping,
configId,
monitorQueryId,
locationId: monLocation,
timestamp: ping['@timestamp'],
};
if (downCount > 0) {
down += 1;
downConfigs[`${configId}-${monLocation}`] = {
...meta,
status: 'down',
};
} else if (upCount > 0) {
up += 1;
upConfigs[`${configId}-${monLocation}`] = {
...meta,
status: 'up',
};
}
const monitorsMissingData = monitorsWithoutData.get(monitorQueryId) || [];
monitorsWithoutData.set(
monitorQueryId,
monitorsMissingData?.filter((loc) => loc !== monLocation)
);
if (!monitorsWithoutData.get(monitorQueryId)?.length) {
monitorsWithoutData.delete(monitorQueryId);
}
}
});
});
},
{ concurrency: 5 }
);
// identify the remaining monitors without data, to determine pending monitors
for (const [queryId, locs] of monitorsWithoutData) {
locs.forEach((loc) => {
pendingConfigs[`${monitorQueryIdToConfigIdMap[queryId]}-${loc}`] = {
configId: `${monitorQueryIdToConfigIdMap[queryId]}`,
monitorQueryId: queryId,
status: 'unknown',
locationId: loc,
};
});
}
return {
up,
down,
pending: Object.values(pendingConfigs).length,
upConfigs,
downConfigs,
pendingConfigs,
enabledMonitorQueryIds: monitorQueryIds,
};
}

View file

@ -10,6 +10,7 @@ import {
SavedObjectsFindResult,
} from '@kbn/core-saved-objects-api-server';
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { queryMonitorStatusForAlert } from './query_monitor_status_alert';
import { SyntheticsServerSetup } from '../../types';
import { SyntheticsEsClient } from '../../lib';
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
@ -17,7 +18,6 @@ import {
getAllMonitors,
processMonitors,
} from '../../saved_objects/synthetics_monitor/get_all_monitors';
import { queryMonitorStatus } from '../../queries/query_monitor_status';
import { StatusRuleParams } from '../../../common/rules/status_rule';
import {
ConfigKey,
@ -107,7 +107,7 @@ export class StatusRuleExecutor {
: 'now-2m';
if (enabledMonitorQueryIds.length > 0) {
const currentStatus = await queryMonitorStatus(
const currentStatus = await queryMonitorStatusForAlert(
this.esClient,
monitorLocationIds,
{

View file

@ -78,7 +78,6 @@ export class SyntheticsEsClient {
const esParams = { index: SYNTHETICS_INDEX_PATTERN, ...params };
const startTime = process.hrtime();
const startTimeNow = Date.now();
let esRequestStatus: RequestStatus = RequestStatus.PENDING;
@ -90,7 +89,8 @@ export class SyntheticsEsClient {
esError = e;
esRequestStatus = RequestStatus.ERROR;
}
if (this.request) {
const isInspectorEnabled = await this.getInspectEnabled();
if (isInspectorEnabled && this.request) {
this.inspectableEsQueries.push(
getInspectResponse({
esError,
@ -102,9 +102,7 @@ export class SyntheticsEsClient {
startTime: startTimeNow,
})
);
}
const isInspectorEnabled = await this.getInspectEnabled();
if (isInspectorEnabled && this.request) {
debugESCall({
startTime,
request: this.request,
@ -125,7 +123,8 @@ export class SyntheticsEsClient {
TSearchRequest extends estypes.SearchRequest = estypes.SearchRequest,
TDocument = unknown
>(
requests: MsearchMultisearchBody[]
requests: MsearchMultisearchBody[],
operationName?: string
): Promise<{ responses: Array<InferSearchResponseOf<TDocument, TSearchRequest>> }> {
const searches: Array<MsearchMultisearchHeader | MsearchMultisearchBody> = [];
for (const request of requests) {
@ -133,15 +132,41 @@ export class SyntheticsEsClient {
searches.push(request);
}
const results = await this.baseESClient.msearch(
{
searches,
},
{ meta: true }
);
const startTimeNow = Date.now();
let res: any;
let esError: any;
try {
res = await this.baseESClient.msearch(
{
searches,
},
{ meta: true }
);
} catch (e) {
esError = e;
}
const isInspectorEnabled = await this.getInspectEnabled();
if (isInspectorEnabled && this.request) {
requests.forEach((request, index) => {
this.inspectableEsQueries.push(
getInspectResponse({
esError,
esRequestParams: { index: SYNTHETICS_INDEX_PATTERN, ...request },
esRequestStatus: RequestStatus.OK,
esResponse: res.body.responses[index],
kibanaRequest: this.request!,
operationName: operationName ?? '',
startTime: startTimeNow,
})
);
});
}
return {
responses: results.body.responses as unknown as Array<
responses: res.body.responses as unknown as Array<
InferSearchResponseOf<TDocument, TSearchRequest>
>,
};
@ -193,6 +218,9 @@ export class SyntheticsEsClient {
return {};
}
async getInspectEnabled() {
if (this.isDev) {
return true;
}
if (!this.uiSettings) {
return false;
}

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import pMap from 'p-map';
import times from 'lodash/times';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { cloneDeep, intersection } from 'lodash';
import { MsearchMultisearchBody } from '@elastic/elasticsearch/lib/api/types';
import { FINAL_SUMMARY_FILTER } from '../../common/constants/client_defaults';
import {
OverviewPendingStatusMetaData,
@ -33,6 +33,99 @@ const fields = [
'tags',
];
const getStatusQuery = ({
idsToQuery,
range,
monitorLocationIds,
idSize,
}: {
idSize: number;
monitorLocationIds: string[];
range: { from: string; to: string };
idsToQuery: string[];
monitorLocationsMap: Record<string, string[]>;
monitorQueryIdToConfigIdMap: Record<string, string>;
}) => {
const params = createEsParams({
body: {
size: 0,
query: {
bool: {
filter: [
FINAL_SUMMARY_FILTER,
{
range: {
'@timestamp': {
gte: range.from,
lte: range.to,
},
},
},
{
terms: {
'monitor.id': idsToQuery,
},
},
] as QueryDslQueryContainer[],
},
},
aggs: {
id: {
terms: {
field: 'monitor.id',
size: idSize,
},
aggs: {
location: {
terms: {
field: 'observer.name',
size: monitorLocationIds.length || 100,
},
aggs: {
status: {
top_hits: {
size: 1,
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
_source: {
includes: fields,
},
},
},
},
},
},
},
},
},
});
if (monitorLocationIds.length > 0) {
params.body.query?.bool?.filter.push({
terms: {
'observer.name': monitorLocationIds,
},
});
}
return params;
};
type StatusQueryParams = ReturnType<typeof getStatusQuery>;
type OverviewStatusResponse = Omit<
OverviewStatus,
| 'disabledCount'
| 'allMonitorsCount'
| 'disabledMonitorsCount'
| 'projectMonitorsCount'
| 'disabledMonitorQueryIds'
| 'allIds'
>;
export async function queryMonitorStatus(
esClient: SyntheticsEsClient,
monitorLocationIds: string[],
@ -40,17 +133,7 @@ export async function queryMonitorStatus(
monitorQueryIds: string[],
monitorLocationsMap: Record<string, string[]>,
monitorQueryIdToConfigIdMap: Record<string, string>
): Promise<
Omit<
OverviewStatus,
| 'disabledCount'
| 'allMonitorsCount'
| 'disabledMonitorsCount'
| 'projectMonitorsCount'
| 'disabledMonitorQueryIds'
| 'allIds'
>
> {
): Promise<OverviewStatusResponse> {
const idSize = Math.trunc(DEFAULT_MAX_ES_BUCKET_SIZE / monitorLocationIds.length || 1);
const pageCount = Math.ceil(monitorQueryIds.length / idSize);
let up = 0;
@ -60,140 +143,80 @@ export async function queryMonitorStatus(
const monitorsWithoutData = new Map(Object.entries(cloneDeep(monitorLocationsMap)));
const pendingConfigs: Record<string, OverviewPendingStatusMetaData> = {};
await pMap(
times(pageCount),
async (i) => {
const idsToQuery = (monitorQueryIds as string[]).slice(i * idSize, i * idSize + idSize);
const params = createEsParams({
body: {
size: 0,
query: {
bool: {
filter: [
FINAL_SUMMARY_FILTER,
{
range: {
'@timestamp': {
gte: range.from,
lte: range.to,
},
},
},
{
terms: {
'monitor.id': idsToQuery,
},
},
] as QueryDslQueryContainer[],
},
},
aggs: {
id: {
terms: {
field: 'monitor.id',
size: idSize,
},
aggs: {
location: {
terms: {
field: 'observer.name',
size: monitorLocationIds.length || 100,
},
aggs: {
status: {
top_hits: {
size: 1,
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
_source: {
includes: fields,
},
},
},
},
},
},
},
},
},
});
const queries: MsearchMultisearchBody[] = times(pageCount).map((i) => {
const idsToQuery = (monitorQueryIds as string[]).slice(i * idSize, i * idSize + idSize);
return getStatusQuery({
idSize,
monitorLocationIds,
range,
idsToQuery,
monitorLocationsMap,
monitorQueryIdToConfigIdMap,
}).body;
});
if (monitorLocationIds.length > 0) {
params.body.query.bool.filter.push({
terms: {
'observer.name': monitorLocationIds,
},
});
}
const { body: result } = await esClient.search<OverviewPing, typeof params>(
params,
'getCurrentStatusOverview' + i
);
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
const locationSummaries = location.buckets.map(({ status, key: locationName }) => {
const ping = status.hits.hits[0]._source;
return { location: locationName, ping };
});
// discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are
// in monitorLocationsMap but not in listOfLocations
const monLocations = monitorLocationsMap?.[queryId];
const monQueriedLocations = intersection(monLocations, monitorLocationIds);
monQueriedLocations?.forEach((monLocation) => {
const locationSummary = locationSummaries.find(
(summary) => summary.location === monLocation
);
if (locationSummary) {
const { ping } = locationSummary;
const downCount = ping.summary?.down ?? 0;
const upCount = ping.summary?.up ?? 0;
const configId = ping.config_id;
const monitorQueryId = ping.monitor.id;
const meta = {
ping,
configId,
monitorQueryId,
locationId: monLocation,
timestamp: ping['@timestamp'],
};
if (downCount > 0) {
down += 1;
downConfigs[`${configId}-${monLocation}`] = {
...meta,
status: 'down',
};
} else if (upCount > 0) {
up += 1;
upConfigs[`${configId}-${monLocation}`] = {
...meta,
status: 'up',
};
}
const monitorsMissingData = monitorsWithoutData.get(monitorQueryId) || [];
monitorsWithoutData.set(
monitorQueryId,
monitorsMissingData?.filter((loc) => loc !== monLocation)
);
if (!monitorsWithoutData.get(monitorQueryId)?.length) {
monitorsWithoutData.delete(monitorQueryId);
}
}
});
});
},
{ concurrency: 5 }
const { responses } = await esClient.msearch<StatusQueryParams, OverviewPing>(
queries,
'getCurrentStatusOverview'
);
responses.forEach((result) => {
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
const locationSummaries = location.buckets.map(({ status, key: locationName }) => {
const ping = status.hits.hits[0]._source;
return { location: locationName, ping };
});
// discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are
// in monitorLocationsMap but not in listOfLocations
const monLocations = monitorLocationsMap?.[queryId];
const monQueriedLocations = intersection(monLocations, monitorLocationIds);
monQueriedLocations?.forEach((monLocation) => {
const locationSummary = locationSummaries.find(
(summary) => summary.location === monLocation
);
if (locationSummary) {
const { ping } = locationSummary;
const downCount = ping.summary?.down ?? 0;
const upCount = ping.summary?.up ?? 0;
const configId = ping.config_id;
const monitorQueryId = ping.monitor.id;
const meta = {
ping,
configId,
monitorQueryId,
locationId: monLocation,
timestamp: ping['@timestamp'],
};
if (downCount > 0) {
down += 1;
downConfigs[`${configId}-${monLocation}`] = {
...meta,
status: 'down',
};
} else if (upCount > 0) {
up += 1;
upConfigs[`${configId}-${monLocation}`] = {
...meta,
status: 'up',
};
}
const monitorsMissingData = monitorsWithoutData.get(monitorQueryId) || [];
monitorsWithoutData.set(
monitorQueryId,
monitorsMissingData?.filter((loc) => loc !== monLocation)
);
if (!monitorsWithoutData.get(monitorQueryId)?.length) {
monitorsWithoutData.delete(monitorQueryId);
}
}
});
});
});
// identify the remaining monitors without data, to determine pending monitors
for (const [queryId, locs] of monitorsWithoutData) {
locs.forEach((loc) => {

View file

@ -108,108 +108,111 @@ describe('current status route', () => {
describe('queryMonitorStatus', () => {
it('parses expected agg fields', async () => {
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
esClient.search.mockResponseOnce(
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
esClient.msearch.mockResponseOnce({
responses: [
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
down: 1,
up: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
down: 1,
up: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
])
);
]),
],
took: 605,
});
expect(
await queryMonitorStatus(
syntheticsEsClient,
@ -261,108 +264,111 @@ describe('current status route', () => {
it('handles limits with multiple requests', async () => {
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
esClient.search.mockResponseOnce(
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
esClient.msearch.mockResponseOnce({
responses: [
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
up: 0,
down: 1,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
up: 0,
down: 1,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
])
);
]),
],
took: 605,
});
/**
* By passing the function a location count of 10k, it forces the query to paginate once,
@ -422,122 +428,125 @@ describe('current status route', () => {
},
pendingConfigs: {},
});
expect(esClient.search).toHaveBeenCalledTimes(2);
expect(esClient.msearch).toHaveBeenCalledTimes(1);
// These assertions are to ensure that we are paginating through the IDs we use for filtering
expect(
// @ts-expect-error mock search is not lining up with expected type
esClient.search.mock.calls[0][0].body.query.bool.filter[2].terms['monitor.id']
esClient.msearch.mock.calls[0][0].searches[1].query.bool.filter[2].terms['monitor.id']
).toEqual(['id1']);
expect(
// @ts-expect-error mock search is not lining up with expected type
esClient.search.mock.calls[1][0].body.query.bool.filter[2].terms['monitor.id']
esClient.msearch.mock.calls[0][0].searches[3].query.bool.filter[2].terms['monitor.id']
).toEqual(['id2']);
});
it('handles pending configs', async () => {
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
esClient.search.mockResponseOnce(
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
esClient.msearch.mockResponseOnce({
responses: [
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
down: 1,
up: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
down: 1,
up: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
])
);
]),
],
took: 605,
});
expect(
await queryMonitorStatus(
syntheticsEsClient,
@ -668,108 +677,111 @@ describe('current status route', () => {
} as unknown as SavedObjectsFindResult<EncryptedSyntheticsMonitorAttributes>,
]);
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
esClient.search.mockResponseOnce(
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
esClient.msearch.mockResponseOnce({
responses: [
getEsResponse([
{
key: 'id1',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:08:16.724Z',
monitor: {
status: 'up',
id: 'id1',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id1',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
{
key: 'id2',
location: {
buckets: [
{
key: 'Asia/Pacific - Japan',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:09:16.724Z',
monitor: {
status: 'up',
id: 'id2',
},
summary: {
up: 1,
down: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Asia/Pacific - Japan',
},
},
},
},
},
],
],
},
},
},
},
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
down: 1,
up: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
{
key: 'Europe - Germany',
status: {
hits: {
hits: [
{
_source: {
'@timestamp': '2022-09-15T16:19:16.724Z',
monitor: {
status: 'down',
id: 'id2',
},
summary: {
down: 1,
up: 0,
},
config_id: 'id2',
observer: {
geo: {
name: 'Europe - Germany',
},
},
},
},
},
],
],
},
},
},
},
],
],
},
},
},
])
);
]),
],
took: 605,
});
const result = await getStatus(
{
syntheticsEsClient,
@ -834,7 +846,7 @@ describe('current status route', () => {
} as unknown as SavedObjectsFindResult<EncryptedSyntheticsMonitorAttributes>,
]);
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
esClient.search.mockResponseOnce(getEsResponse([]));
esClient.msearch.mockResponseOnce({ responses: [getEsResponse([])], took: 605 });
expect(
await getStatus(
{