diff --git a/App/FeatureSet/Dashboard/src/Components/Monitor/MonitorCustomMetrics.tsx b/App/FeatureSet/Dashboard/src/Components/Monitor/MonitorCustomMetrics.tsx new file mode 100644 index 0000000000..7c0aa9a607 --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Components/Monitor/MonitorCustomMetrics.tsx @@ -0,0 +1,205 @@ +import React, { + FunctionComponent, + ReactElement, + useCallback, + useEffect, + useState, +} from "react"; +import ObjectID from "Common/Types/ObjectID"; +import MetricView from "../Metrics/MetricView"; +import ProjectUtil from "Common/UI/Utils/Project"; +import API from "Common/UI/Utils/API/API"; +import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI"; +import PageLoader from "Common/UI/Components/Loader/PageLoader"; +import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import AggregationType from "Common/Types/BaseDatabase/AggregationType"; +import MetricQueryConfigData from "Common/Types/Metrics/MetricQueryConfigData"; +import MetricViewData from "Common/Types/Metrics/MetricViewData"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import RangeStartAndEndDateTime, { + RangeStartAndEndDateTimeUtil, +} from "Common/Types/Time/RangeStartAndEndDateTime"; +import TimeRange from "Common/Types/Time/TimeRange"; +import RangeStartAndEndDateView from "Common/UI/Components/Date/RangeStartAndEndDateView"; +import Card from "Common/UI/Components/Card/Card"; +import MetricType from "Common/Models/DatabaseModels/MetricType"; +import ListResult from "Common/Types/BaseDatabase/ListResult"; +import Search from "Common/Types/BaseDatabase/Search"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import EmptyState from "Common/UI/Components/EmptyState/EmptyState"; +import IconProp from "Common/Types/Icon/IconProp"; + +export interface ComponentProps { + monitorId: ObjectID; +} + +const MonitorCustomMetrics: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(""); + const [customMetricNames, setCustomMetricNames] = useState>([]); + + const fetchCustomMetricNames: PromiseVoidFunction = + async (): Promise => { + setIsLoading(true); + + try { + const listResult: ListResult = + await ModelAPI.getList({ + modelType: MetricType, + query: { + projectId: ProjectUtil.getCurrentProjectId()!, + name: new Search("custom.monitor.") as any, + }, + select: { + name: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + sort: { + name: SortOrder.Ascending, + }, + }); + + const names: Array = listResult.data + .map((mt: MetricType) => { + return mt.name || ""; + }) + .filter((name: string) => { + return name.length > 0; + }); + + setCustomMetricNames(names); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchCustomMetricNames().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); + + const [timeRange, setTimeRange] = useState({ + range: TimeRange.PAST_ONE_HOUR, + }); + + const getQueryConfigs: () => Array = + (): Array => { + return customMetricNames.map((metricName: string): MetricQueryConfigData => { + const displayName: string = metricName.replace("custom.monitor.", ""); + + return { + metricAliasData: { + metricVariable: metricName, + title: displayName, + description: `Custom metric: ${displayName}`, + legend: displayName, + legendUnit: "", + }, + metricQueryData: { + filterData: { + metricName: metricName, + attributes: { + monitorId: props.monitorId.toString(), + projectId: + ProjectUtil.getCurrentProjectId()?.toString() || "", + }, + aggegationType: AggregationType.Avg, + }, + groupBy: { + attributes: true, + }, + }, + }; + }); + }; + + const [metricViewData, setMetricViewData] = useState({ + startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate({ + range: TimeRange.PAST_ONE_HOUR, + }), + queryConfigs: [], + formulaConfigs: [], + }); + + useEffect(() => { + if (customMetricNames.length > 0) { + setMetricViewData({ + startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate( + timeRange, + ), + queryConfigs: getQueryConfigs(), + formulaConfigs: [], + }); + } + }, [customMetricNames]); + + const handleTimeRangeChange: ( + newTimeRange: RangeStartAndEndDateTime, + ) => void = useCallback((newTimeRange: RangeStartAndEndDateTime): void => { + setTimeRange(newTimeRange); + const dateRange: InBetween = + RangeStartAndEndDateTimeUtil.getStartAndEndDate(newTimeRange); + setMetricViewData((prev: MetricViewData) => { + return { + ...prev, + startAndEndDate: dateRange, + }; + }); + }, []); + + if (isLoading) { + return ; + } + + if (error) { + return ; + } + + if (customMetricNames.length === 0) { + return ( + + ); + } + + return ( + + } + > + { + setMetricViewData({ + ...data, + queryConfigs: getQueryConfigs(), + formulaConfigs: [], + }); + }} + /> + + ); +}; + +export default MonitorCustomMetrics; diff --git a/App/FeatureSet/Dashboard/src/Pages/Monitor/View/Metrics.tsx b/App/FeatureSet/Dashboard/src/Pages/Monitor/View/Metrics.tsx index 00e5a02e23..d997c33184 100644 --- a/App/FeatureSet/Dashboard/src/Pages/Monitor/View/Metrics.tsx +++ b/App/FeatureSet/Dashboard/src/Pages/Monitor/View/Metrics.tsx @@ -1,5 +1,6 @@ import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; import MonitorMetricsElement from "../../../Components/Monitor/MonitorMetrics"; +import MonitorCustomMetrics from "../../../Components/Monitor/MonitorCustomMetrics"; import MonitorIncidentMetrics from "../../../Components/Monitor/MonitorIncidentMetrics"; import MonitorAlertMetrics from "../../../Components/Monitor/MonitorAlertMetrics"; import PageComponentProps from "../../PageComponentProps"; @@ -71,6 +72,16 @@ const MonitorMetrics: FunctionComponent< }); } + if ( + monitorType === MonitorType.CustomJavaScriptCode || + monitorType === MonitorType.SyntheticMonitor + ) { + tabs.push({ + name: "Custom Metrics", + children: , + }); + } + tabs.push({ name: "Incident Metrics", children: , diff --git a/Common/Server/Utils/Monitor/MonitorMetricUtil.ts b/Common/Server/Utils/Monitor/MonitorMetricUtil.ts index 23d1493b9e..de6b9a0c45 100644 --- a/Common/Server/Utils/Monitor/MonitorMetricUtil.ts +++ b/Common/Server/Utils/Monitor/MonitorMetricUtil.ts @@ -554,6 +554,12 @@ export default class MonitorMetricUtil { ...syntheticCustomMetrics, ].slice(0, 100); + if (allCustomMetrics.length > 0) { + logger.debug( + `${data.monitorId.toString()} - Processing ${allCustomMetrics.length} custom metrics`, + ); + } + const reservedAttributeKeys: Set = new Set([ "monitorId", "projectId", diff --git a/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts index 2a118f13ee..7a9b3e7f91 100644 --- a/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts @@ -63,6 +63,12 @@ export default class CustomCodeMonitor { scriptResult.logMessages = result.logMessages; scriptResult.capturedMetrics = result.capturedMetrics || []; + if (scriptResult.capturedMetrics.length > 0) { + logger.debug( + `Custom Code Monitor ${options.monitorId?.toString()} - Captured ${scriptResult.capturedMetrics.length} custom metrics`, + ); + } + scriptResult.result = result?.returnValue?.data; } catch (err) { logger.error(err); diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts index a2673962f6..c51d6ab369 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts @@ -182,6 +182,12 @@ export default class SyntheticMonitor { scriptResult.logMessages = result.logMessages; scriptResult.capturedMetrics = result.capturedMetrics || []; + if (scriptResult.capturedMetrics.length > 0) { + logger.debug( + `Synthetic Monitor - Captured ${scriptResult.capturedMetrics.length} custom metrics`, + ); + } + if (result.returnValue?.screenshots) { if (!scriptResult.screenshots) { scriptResult.screenshots = {};