From a582794ace6a7d951f73aea58afb010c108a7985 Mon Sep 17 00:00:00 2001 From: shahzad Date: Tue, 25 Feb 2020 16:24:15 +0100 Subject: [PATCH] update conflicts --- .../lib/requests/get_monitor_duration.ts | 170 ++++++++++++++++++ .../plugins/uptime/server/rest_api/index.ts | 1 + 2 files changed, 171 insertions(+) create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts new file mode 100644 index 0000000000000..d58b22d48ad36 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UMElasticsearchQueryFn } from '../adapters'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; +import { getHistogramIntervalFormatted } from '../helper'; +import { LocationDurationLine, MonitorChart } from '../../../common/types'; + +export interface GetMonitorChartsParams { + /** @member monitorId ID value for the selected monitor */ + monitorId: string; + /** @member dateStart timestamp bounds */ + dateStart: string; + /** @member dateRangeEnd timestamp bounds */ + dateEnd: string; +} + +const formatStatusBuckets = (time: any, buckets: any, docCount: any) => { + let up = null; + let down = null; + + buckets.forEach((bucket: any) => { + if (bucket.key === 'up') { + up = bucket.doc_count; + } else if (bucket.key === 'down') { + down = bucket.doc_count; + } + }); + + return { + x: time, + up, + down, + total: docCount, + }; +}; + +/** + * Fetches data used to populate monitor charts + */ +export const getMonitorDurationChart: UMElasticsearchQueryFn< + GetMonitorChartsParams, + MonitorChart +> = async ({ callES, dateStart, dateEnd, monitorId }) => { + const params = { + index: INDEX_NAMES.HEARTBEAT, + body: { + query: { + bool: { + filter: [ + { range: { '@timestamp': { gte: dateStart, lte: dateEnd } } }, + { term: { 'monitor.id': monitorId } }, + { term: { 'monitor.status': 'up' } }, + ], + }, + }, + size: 0, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: getHistogramIntervalFormatted(dateStart, dateEnd), + min_doc_count: 0, + }, + aggs: { + location: { + terms: { + field: 'observer.geo.name', + missing: 'N/A', + }, + aggs: { + status: { terms: { field: 'monitor.status', size: 2, shard_size: 2 } }, + duration: { stats: { field: 'monitor.duration.us' } }, + }, + }, + }, + }, + }, + }, + }; + + const result = await callES('search', params); + + const dateHistogramBuckets: any[] = result?.aggregations?.timeseries?.buckets ?? []; + + /** + * The code below is responsible for formatting the aggregation data we fetched above in a way + * that the chart components used by the client understands. + * There are five required values. Two are lists of points that conform to a simple (x,y) structure. + * + * The third list is for an area chart expressing a range, and it requires an (x,y,y0) structure, + * where y0 is the min value for the point and y is the max. + * + * Additionally, we supply the maximum value for duration and status, so the corresponding charts know + * what the domain size should be. + */ + const monitorChartsData: MonitorChart = { + locationDurationLines: [], + status: [], + durationMaxValue: 0, + statusMaxCount: 0, + }; + + /** + * The following section of code enables us to provide buckets per location + * that have a `null` value if there is no data at the given timestamp. + * + * We maintain two `Set`s. One is per bucket, the other is persisted for the + * entire collection. At the end of a bucket's evaluation, if there was no object + * parsed for a given location line that was already started, we insert an element + * to the given line with a null value. Without this, our charts on the client will + * display a continuous line for each of the points they are provided. + */ + + // a set of all the locations found for this result + const resultLocations = new Set(); + const linesByLocation: { [key: string]: LocationDurationLine } = {}; + dateHistogramBuckets.forEach(dateHistogramBucket => { + const x = dateHistogramBucket.key; + const docCount = dateHistogramBucket?.doc_count ?? 0; + // a set of all the locations for the current bucket + const bucketLocations = new Set(); + + dateHistogramBucket.location.buckets.forEach( + (locationBucket: { key: string; duration: { avg: number } }) => { + const locationName = locationBucket.key; + // store the location name in each set + bucketLocations.add(locationName); + resultLocations.add(locationName); + + // create a new line for this location if it doesn't exist + let currentLine: LocationDurationLine = linesByLocation?.[locationName] ?? undefined; + if (!currentLine) { + currentLine = { name: locationName, line: [] }; + linesByLocation[locationName] = currentLine; + monitorChartsData.locationDurationLines.push(currentLine); + } + // add the entry for the current location's duration average + currentLine.line.push({ x, y: locationBucket?.duration?.avg ?? null }); + } + ); + + // if there are more lines in the result than are represented in the current bucket, + // we must add null entries + if (dateHistogramBucket.location.buckets.length < resultLocations.size) { + resultLocations.forEach(resultLocation => { + // the current bucket had no value for this location, insert a null value + if (!bucketLocations.has(resultLocation)) { + const locationLine = monitorChartsData.locationDurationLines.find( + ({ name }) => name === resultLocation + ); + // in practice, there should always be a line present, but `find` can return `undefined` + if (locationLine) { + // this will create a gap in the line like we desire + locationLine.line.push({ x, y: null }); + } + } + }); + } + + monitorChartsData.status.push( + formatStatusBuckets(x, dateHistogramBucket?.status?.buckets ?? [], docCount) + ); + }); + + return monitorChartsData; +}; diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index ba031a1229a2f..69981b7860d59 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -22,6 +22,7 @@ import { createGetMonitorDurationRoute } from './monitors/monitors_durations'; export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; export { uptimeRouteWrapper } from './uptime_route_wrapper'; + export const restApiRoutes: UMRestApiRouteFactory[] = [ createGetOverviewFilters, createGetPingsRoute,