@oneuptime/common 11.3.28 → 11.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AIRun.ts +50 -0
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Server/API/AIAgentTaskAPI.ts +10 -24
- package/Server/API/AIInvestigationAPI.ts +148 -0
- package/Server/API/TelemetryAPI.ts +4 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AIAgentTaskService.ts +62 -0
- package/Server/Services/AlertService.ts +25 -0
- package/Server/Services/AnalyticsDatabaseService.ts +27 -0
- package/Server/Services/IncidentService.ts +25 -0
- package/Server/Services/IncidentStateTimelineService.ts +19 -0
- package/Server/Services/LogAggregationService.ts +29 -11
- package/Server/Services/MetricService.ts +603 -13
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
- package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
- package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
- package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
- package/Server/Utils/AI/Sentinel/README.md +10 -0
- package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
- package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
- package/Server/Utils/AI/Toolbox/Index.ts +11 -0
- package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +44 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +108 -2
- package/Tests/Server/API/BaseAPI.test.ts +1 -0
- package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +146 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +22 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +194 -0
- package/Tests/Types/AI/AIAgentTaskStatus.test.ts +86 -0
- package/Tests/Types/BaseDatabase/AggregationIntervalUtil.test.ts +127 -0
- package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.test.ts +152 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
- package/Tests/Types/Trace/TraceRecordingRuleDefinition.test.ts +308 -0
- package/Tests/UI/Components/Dropdown.test.tsx +17 -0
- package/Tests/UI/Components/EntityDropdown.test.tsx +130 -0
- package/Tests/UI/Components/Modal.test.tsx +144 -2
- package/Tests/UI/Components/TelemetrySearchBar.test.tsx +90 -0
- package/Tests/UI/Utils/Theme.test.ts +161 -0
- package/Types/BaseDatabase/AggregateBy.ts +12 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +19 -0
- package/Types/Dashboard/DashboardTemplates.ts +9 -1
- package/Types/Date.ts +46 -0
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/HttpPhaseTimings.ts +16 -0
- package/Types/Monitor/MonitorMetricType.ts +17 -0
- package/Types/Monitor/PingMonitor/PingMonitorResponse.ts +20 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
- package/Types/Probe/ProbeMonitorResponse.ts +14 -0
- package/UI/Components/Calendar/Calendar.tsx +8 -2
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -5
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +4 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +6 -3
- package/UI/Components/CodeBlock/CodeBlock.tsx +2 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
- package/UI/Components/Divider/Divider.tsx +1 -1
- package/UI/Components/Dropdown/Dropdown.tsx +13 -4
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +110 -2
- package/UI/Components/ErrorBoundary.tsx +2 -1
- package/UI/Components/EventItem/EventItem.tsx +2 -2
- package/UI/Components/GanttChart/Bar/Index.tsx +1 -1
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +30 -23
- package/UI/Components/Header/Header.tsx +7 -0
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +2 -0
- package/UI/Components/Label/Label.tsx +2 -2
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +22 -10
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +9 -3
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +8 -8
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +44 -20
- package/UI/Components/Modal/ConfirmModal.tsx +1 -1
- package/UI/Components/Modal/Modal.tsx +213 -121
- package/UI/Components/Modal/ModalFooter.tsx +33 -22
- package/UI/Components/ModelTable/TableView.tsx +1 -1
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +20 -12
- package/UI/Components/Pill/Pill.tsx +21 -13
- package/UI/Components/StatusBubble/StatusBubble.tsx +3 -3
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +7 -1
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +9 -3
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +62 -4
- package/UI/Components/Workflow/Component.tsx +53 -33
- package/UI/Components/Workflow/ComponentPortViewer.tsx +6 -6
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +8 -8
- package/UI/Components/Workflow/ComponentsModal.tsx +16 -9
- package/UI/Components/Workflow/Workflow.tsx +13 -11
- package/UI/Utils/Theme.ts +140 -0
- package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +104 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/Monitor/MonitorMetricType.ts +70 -2
- package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskAPI.js +8 -23
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIAgentTaskService.js +59 -0
- package/build/dist/Server/Services/AIAgentTaskService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +24 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +19 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +24 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +21 -7
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +449 -7
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +6 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
- package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +29 -0
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +86 -2
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +9 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Date.js +32 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/HttpPhaseTimings.js +2 -0
- package/build/dist/Types/Monitor/HttpPhaseTimings.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +15 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
- package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +7 -4
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +4 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +6 -3
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +3 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/Divider/Divider.js +1 -1
- package/build/dist/UI/Components/Divider/Divider.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +10 -4
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +76 -3
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +2 -1
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
- package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +29 -23
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Header/Header.js +1 -0
- package/build/dist/UI/Components/Header/Header.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Label/Label.js +2 -2
- package/build/dist/UI/Components/Label/Label.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +20 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +8 -2
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +8 -8
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +40 -20
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +109 -51
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +13 -10
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +17 -12
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/Pill/Pill.js +12 -13
- package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js +3 -3
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +36 -6
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +43 -33
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentPortViewer.js +6 -6
- package/build/dist/UI/Components/Workflow/ComponentPortViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +8 -8
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +15 -9
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +13 -11
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Utils/Theme.js +106 -0
- package/build/dist/UI/Utils/Theme.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +92 -0
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +70 -3
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,9 @@ import BasicInfrastructureMetrics, {
|
|
|
14
14
|
import Dictionary from "../../../Types/Dictionary";
|
|
15
15
|
import { JSONObject } from "../../../Types/JSON";
|
|
16
16
|
import CapturedMetric from "../../../Types/Monitor/CustomCodeMonitor/CapturedMetric";
|
|
17
|
+
import HttpPhaseTimings from "../../../Types/Monitor/HttpPhaseTimings";
|
|
17
18
|
import MonitorMetricType from "../../../Types/Monitor/MonitorMetricType";
|
|
19
|
+
import PingMonitorResponse from "../../../Types/Monitor/PingMonitor/PingMonitorResponse";
|
|
18
20
|
import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
|
|
19
21
|
import ServerMonitorResponse from "../../../Types/Monitor/ServerMonitor/ServerMonitorResponse";
|
|
20
22
|
import SyntheticMonitorResponse from "../../../Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse";
|
|
@@ -23,8 +25,12 @@ import ObjectID from "../../../Types/ObjectID";
|
|
|
23
25
|
import OneUptimeDate from "../../../Types/Date";
|
|
24
26
|
|
|
25
27
|
export default class MonitorMetricUtil {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
/*
|
|
29
|
+
* Default retention in days if GlobalConfig is not set. 30 days keeps
|
|
30
|
+
* enough history for trend analysis and evaluate-over-time criteria;
|
|
31
|
+
* one day made week-over-week charts impossible out of the box.
|
|
32
|
+
*/
|
|
33
|
+
private static readonly DEFAULT_RETENTION_DAYS: number = 30;
|
|
28
34
|
|
|
29
35
|
// Cached retention value to avoid querying GlobalConfig on every monitor check
|
|
30
36
|
private static cachedRetentionDays: number | null = null;
|
|
@@ -883,6 +889,106 @@ export default class MonitorMetricUtil {
|
|
|
883
889
|
metricNameServiceNameMap[MonitorMetricType.ResponseTime] = metricType;
|
|
884
890
|
}
|
|
885
891
|
|
|
892
|
+
if ((data.dataToProcess as ProbeMonitorResponse).httpTimings) {
|
|
893
|
+
const httpTimings: HttpPhaseTimings = (
|
|
894
|
+
data.dataToProcess as ProbeMonitorResponse
|
|
895
|
+
).httpTimings!;
|
|
896
|
+
|
|
897
|
+
const extraAttributes: JSONObject = {
|
|
898
|
+
probeId: (
|
|
899
|
+
data.dataToProcess as ProbeMonitorResponse
|
|
900
|
+
).probeId.toString(),
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
const phaseMetrics: Array<{
|
|
904
|
+
metricName: MonitorMetricType;
|
|
905
|
+
value: number | undefined;
|
|
906
|
+
description: string;
|
|
907
|
+
}> = [
|
|
908
|
+
{
|
|
909
|
+
metricName: MonitorMetricType.DnsLookupTime,
|
|
910
|
+
value: httpTimings.dnsLookupInMs,
|
|
911
|
+
description: "DNS lookup time for this monitor",
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
metricName: MonitorMetricType.TcpConnectTime,
|
|
915
|
+
value: httpTimings.tcpConnectInMs,
|
|
916
|
+
description: "TCP connect time for this monitor",
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
metricName: MonitorMetricType.TlsHandshakeTime,
|
|
920
|
+
value: httpTimings.tlsHandshakeInMs,
|
|
921
|
+
description: "TLS handshake time for this monitor",
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
metricName: MonitorMetricType.TimeToFirstByte,
|
|
925
|
+
value: httpTimings.timeToFirstByteInMs,
|
|
926
|
+
description: "Time to first byte for this monitor",
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
metricName: MonitorMetricType.DownloadTime,
|
|
930
|
+
value: httpTimings.downloadInMs,
|
|
931
|
+
description: "Response download time for this monitor",
|
|
932
|
+
},
|
|
933
|
+
];
|
|
934
|
+
|
|
935
|
+
for (const phaseMetric of phaseMetrics) {
|
|
936
|
+
await this.pushMonitorMetric({
|
|
937
|
+
projectId: data.projectId,
|
|
938
|
+
monitorId: data.monitorId,
|
|
939
|
+
monitorName: data.monitorName,
|
|
940
|
+
probeName: data.probeName,
|
|
941
|
+
metricName: phaseMetric.metricName,
|
|
942
|
+
value: phaseMetric.value,
|
|
943
|
+
description: phaseMetric.description,
|
|
944
|
+
unit: "ms",
|
|
945
|
+
extraAttributes: extraAttributes,
|
|
946
|
+
metricRows: metricRows,
|
|
947
|
+
metricNameServiceNameMap: metricNameServiceNameMap,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if ((data.dataToProcess as ProbeMonitorResponse).pingResponse) {
|
|
953
|
+
const pingResponse: PingMonitorResponse = (
|
|
954
|
+
data.dataToProcess as ProbeMonitorResponse
|
|
955
|
+
).pingResponse!;
|
|
956
|
+
|
|
957
|
+
const extraAttributes: JSONObject = {
|
|
958
|
+
probeId: (
|
|
959
|
+
data.dataToProcess as ProbeMonitorResponse
|
|
960
|
+
).probeId.toString(),
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
await this.pushMonitorMetric({
|
|
964
|
+
projectId: data.projectId,
|
|
965
|
+
monitorId: data.monitorId,
|
|
966
|
+
monitorName: data.monitorName,
|
|
967
|
+
probeName: data.probeName,
|
|
968
|
+
metricName: MonitorMetricType.PacketLossPercent,
|
|
969
|
+
value: pingResponse.packetLossPercent,
|
|
970
|
+
description: CheckOn.PacketLossPercent + " for this monitor",
|
|
971
|
+
unit: "%",
|
|
972
|
+
extraAttributes: extraAttributes,
|
|
973
|
+
metricRows: metricRows,
|
|
974
|
+
metricNameServiceNameMap: metricNameServiceNameMap,
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
await this.pushMonitorMetric({
|
|
978
|
+
projectId: data.projectId,
|
|
979
|
+
monitorId: data.monitorId,
|
|
980
|
+
monitorName: data.monitorName,
|
|
981
|
+
probeName: data.probeName,
|
|
982
|
+
metricName: MonitorMetricType.Jitter,
|
|
983
|
+
value: pingResponse.jitterInMs,
|
|
984
|
+
description: CheckOn.Jitter + " for this monitor",
|
|
985
|
+
unit: "ms",
|
|
986
|
+
extraAttributes: extraAttributes,
|
|
987
|
+
metricRows: metricRows,
|
|
988
|
+
metricNameServiceNameMap: metricNameServiceNameMap,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
|
|
886
992
|
if ((data.dataToProcess as ProbeMonitorResponse).isOnline !== undefined) {
|
|
887
993
|
const extraAttributes: JSONObject = {
|
|
888
994
|
probeId: (
|
|
@@ -103,6 +103,7 @@ jest.mock("../../../Server/Services/ProjectService", () => {
|
|
|
103
103
|
|
|
104
104
|
jest.mock("../../../Server/EnvironmentConfig", () => {
|
|
105
105
|
return {
|
|
106
|
+
...jest.requireActual("../../../Server/EnvironmentConfig"),
|
|
106
107
|
IsBillingEnabled: true,
|
|
107
108
|
LogLevel: "INFO", // Use string literal instead of ConfigLogLevel.INFO
|
|
108
109
|
DisableTelemetry: true,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import AIAgentTaskService from "../../../Server/Services/AIAgentTaskService";
|
|
2
|
+
import Model from "../../../Models/DatabaseModels/AIAgentTask";
|
|
3
|
+
import AIAgentTaskStatus from "../../../Types/AI/AIAgentTaskStatus";
|
|
4
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
5
|
+
import { describe, expect, test, afterEach } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* claimNextScheduledTask is the atomic-claim fix for the get-pending-task
|
|
9
|
+
* double-processing race: multiple KEDA-scaled AIAgent workers poll the same
|
|
10
|
+
* endpoint, and the old plain findOneBy returned the oldest Scheduled task
|
|
11
|
+
* without mutating it, so two workers could both receive — and both execute —
|
|
12
|
+
* the same task.
|
|
13
|
+
*
|
|
14
|
+
* The claim collapses the read-then-take into findOneBy + a status-guarded
|
|
15
|
+
* updateOneBy (Scheduled -> InProgress). updateOneBy returns the number of
|
|
16
|
+
* rows changed: 1 means this caller won the claim; 0 means another worker
|
|
17
|
+
* already took the task, and the loop retries with the next candidate.
|
|
18
|
+
*
|
|
19
|
+
* These tests mock the two inherited persistence helpers (no Postgres) to
|
|
20
|
+
* lock in:
|
|
21
|
+
* (a) a won claim returns the task, with the guard and the InProgress
|
|
22
|
+
* transition on the exact row that was read;
|
|
23
|
+
* (b) a lost claim (0 rows) is never returned — the loop moves to the
|
|
24
|
+
* next candidate;
|
|
25
|
+
* (c) no Scheduled tasks => null, with no write at all;
|
|
26
|
+
* (d) the retry loop is bounded, so a pathological all-losses storm
|
|
27
|
+
* terminates.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
function fakeTask(id: ObjectID): Model {
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
_id: id.toString(),
|
|
34
|
+
projectId: ObjectID.generate(),
|
|
35
|
+
taskType: "FixException",
|
|
36
|
+
} as unknown as Model;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("AIAgentTaskService.claimNextScheduledTask", () => {
|
|
40
|
+
const aiAgentId: ObjectID = ObjectID.generate();
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
jest.restoreAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("returns the task when the claim wins, guarding on Scheduled status", async () => {
|
|
47
|
+
const task: Model = fakeTask(ObjectID.generate());
|
|
48
|
+
|
|
49
|
+
const findOneBy: jest.SpyInstance = jest
|
|
50
|
+
.spyOn(AIAgentTaskService, "findOneBy")
|
|
51
|
+
.mockResolvedValue(task);
|
|
52
|
+
const updateOneBy: jest.SpyInstance = jest
|
|
53
|
+
.spyOn(AIAgentTaskService, "updateOneBy")
|
|
54
|
+
.mockResolvedValue(1);
|
|
55
|
+
|
|
56
|
+
const claimed: Model | null =
|
|
57
|
+
await AIAgentTaskService.claimNextScheduledTask({
|
|
58
|
+
aiAgentId: aiAgentId,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(claimed).toBe(task);
|
|
62
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(findOneBy).toHaveBeenCalledWith(
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
query: { status: AIAgentTaskStatus.Scheduled },
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
expect(updateOneBy).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(updateOneBy).toHaveBeenCalledWith(
|
|
70
|
+
expect.objectContaining({
|
|
71
|
+
query: {
|
|
72
|
+
_id: task.id!.toString(),
|
|
73
|
+
status: AIAgentTaskStatus.Scheduled,
|
|
74
|
+
},
|
|
75
|
+
data: expect.objectContaining({
|
|
76
|
+
status: AIAgentTaskStatus.InProgress,
|
|
77
|
+
aiAgentId: aiAgentId,
|
|
78
|
+
startedAt: expect.any(Date),
|
|
79
|
+
}),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("retries with the next candidate when another worker wins the race", async () => {
|
|
85
|
+
const lostTask: Model = fakeTask(ObjectID.generate());
|
|
86
|
+
const wonTask: Model = fakeTask(ObjectID.generate());
|
|
87
|
+
|
|
88
|
+
const findOneBy: jest.SpyInstance = jest
|
|
89
|
+
.spyOn(AIAgentTaskService, "findOneBy")
|
|
90
|
+
.mockResolvedValueOnce(lostTask)
|
|
91
|
+
.mockResolvedValueOnce(wonTask);
|
|
92
|
+
const updateOneBy: jest.SpyInstance = jest
|
|
93
|
+
.spyOn(AIAgentTaskService, "updateOneBy")
|
|
94
|
+
.mockResolvedValueOnce(0)
|
|
95
|
+
.mockResolvedValueOnce(1);
|
|
96
|
+
|
|
97
|
+
const claimed: Model | null =
|
|
98
|
+
await AIAgentTaskService.claimNextScheduledTask({
|
|
99
|
+
aiAgentId: aiAgentId,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(claimed).toBe(wonTask);
|
|
103
|
+
expect(findOneBy).toHaveBeenCalledTimes(2);
|
|
104
|
+
expect(updateOneBy).toHaveBeenCalledTimes(2);
|
|
105
|
+
expect(updateOneBy).toHaveBeenLastCalledWith(
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
query: {
|
|
108
|
+
_id: wonTask.id!.toString(),
|
|
109
|
+
status: AIAgentTaskStatus.Scheduled,
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("returns null without writing when no Scheduled task exists", async () => {
|
|
116
|
+
jest.spyOn(AIAgentTaskService, "findOneBy").mockResolvedValue(null);
|
|
117
|
+
const updateOneBy: jest.SpyInstance = jest
|
|
118
|
+
.spyOn(AIAgentTaskService, "updateOneBy")
|
|
119
|
+
.mockResolvedValue(1);
|
|
120
|
+
|
|
121
|
+
const claimed: Model | null =
|
|
122
|
+
await AIAgentTaskService.claimNextScheduledTask({
|
|
123
|
+
aiAgentId: aiAgentId,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(claimed).toBeNull();
|
|
127
|
+
expect(updateOneBy).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("gives up after a bounded number of lost claims", async () => {
|
|
131
|
+
const findOneBy: jest.SpyInstance = jest
|
|
132
|
+
.spyOn(AIAgentTaskService, "findOneBy")
|
|
133
|
+
.mockImplementation(async () => {
|
|
134
|
+
return fakeTask(ObjectID.generate());
|
|
135
|
+
});
|
|
136
|
+
jest.spyOn(AIAgentTaskService, "updateOneBy").mockResolvedValue(0);
|
|
137
|
+
|
|
138
|
+
const claimed: Model | null =
|
|
139
|
+
await AIAgentTaskService.claimNextScheduledTask({
|
|
140
|
+
aiAgentId: aiAgentId,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(claimed).toBeNull();
|
|
144
|
+
expect(findOneBy).toHaveBeenCalledTimes(5);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -98,6 +98,7 @@ describe("LogAggregationService", () => {
|
|
|
98
98
|
).buildHistogramStatement({
|
|
99
99
|
...defaultRequest,
|
|
100
100
|
facetKey: undefined,
|
|
101
|
+
bucketSizeInMinutes: 60,
|
|
101
102
|
attributes: { requestid: "uuid-123" },
|
|
102
103
|
});
|
|
103
104
|
|
|
@@ -110,4 +111,25 @@ describe("LogAggregationService", () => {
|
|
|
110
111
|
expect(Object.values(statement.query_params)).toContain("requestid");
|
|
111
112
|
expect(Object.values(statement.query_params)).toContain("uuid-123");
|
|
112
113
|
});
|
|
114
|
+
|
|
115
|
+
test("histogram supports multi-value dashboard attribute filters", () => {
|
|
116
|
+
const statement: Statement = (
|
|
117
|
+
LogAggregationService as any
|
|
118
|
+
).buildHistogramStatement({
|
|
119
|
+
...defaultRequest,
|
|
120
|
+
facetKey: undefined,
|
|
121
|
+
bucketSizeInMinutes: 60,
|
|
122
|
+
attributes: { region: ["eu-west-1", "us-east-1"] },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(statement.query).toContain(
|
|
126
|
+
"arrayExists((k, v) -> lowerUTF8(k) = lowerUTF8(",
|
|
127
|
+
);
|
|
128
|
+
expect(statement.query).toContain("AND v IN (");
|
|
129
|
+
expect(Object.values(statement.query_params)).toContain("region");
|
|
130
|
+
expect(Object.values(statement.query_params)).toContainEqual([
|
|
131
|
+
"eu-west-1",
|
|
132
|
+
"us-east-1",
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
113
135
|
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import "../TestingUtils/Init";
|
|
2
|
+
import { MetricService } from "../../../Server/Services/MetricService";
|
|
3
|
+
import Metric from "../../../Models/AnalyticsModels/Metric";
|
|
4
|
+
import AggregateBy from "../../../Server/Types/AnalyticsDatabase/AggregateBy";
|
|
5
|
+
import { Statement } from "../../../Server/Utils/AnalyticsDatabase/Statement";
|
|
6
|
+
import AggregationType from "../../../Types/BaseDatabase/AggregationType";
|
|
7
|
+
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
8
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
9
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
10
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
11
|
+
|
|
12
|
+
describe("MetricService aggregate statement generation", () => {
|
|
13
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
14
|
+
const startDate: Date = new Date("2026-07-09T19:08:00.000Z");
|
|
15
|
+
const endDate: Date = new Date("2026-07-09T19:13:00.000Z");
|
|
16
|
+
|
|
17
|
+
let service: MetricService;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
service = new MetricService();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
type BuildAggregateByFunction = (
|
|
24
|
+
overrides?: Partial<AggregateBy<Metric>>,
|
|
25
|
+
) => AggregateBy<Metric>;
|
|
26
|
+
|
|
27
|
+
const buildAggregateBy: BuildAggregateByFunction = (
|
|
28
|
+
overrides?: Partial<AggregateBy<Metric>>,
|
|
29
|
+
): AggregateBy<Metric> => {
|
|
30
|
+
return {
|
|
31
|
+
query: {
|
|
32
|
+
projectId: projectId,
|
|
33
|
+
name: "http.client.request.duration",
|
|
34
|
+
time: new InBetween(startDate, endDate),
|
|
35
|
+
} as AggregateBy<Metric>["query"],
|
|
36
|
+
aggregationType: AggregationType.Avg,
|
|
37
|
+
aggregateColumnName: "value",
|
|
38
|
+
aggregationTimestampColumnName: "time",
|
|
39
|
+
startTimestamp: startDate,
|
|
40
|
+
endTimestamp: endDate,
|
|
41
|
+
limit: 10000,
|
|
42
|
+
skip: 0,
|
|
43
|
+
sort: { time: SortOrder.Ascending } as AggregateBy<Metric>["sort"],
|
|
44
|
+
props: { isRoot: true },
|
|
45
|
+
...overrides,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type GetQueryFunction = (aggregateBy: AggregateBy<Metric>) => string;
|
|
50
|
+
|
|
51
|
+
const getQuery: GetQueryFunction = (
|
|
52
|
+
aggregateBy: AggregateBy<Metric>,
|
|
53
|
+
): string => {
|
|
54
|
+
const result: { statement: Statement; columns: Array<string> } =
|
|
55
|
+
service.toAggregateStatement(aggregateBy);
|
|
56
|
+
return result.statement.query;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
describe("scalar aggregations", () => {
|
|
60
|
+
it("routes plain Avg (no filters, no group-by, no distribution hint) to the minute MV", () => {
|
|
61
|
+
const query: string = getQuery(buildAggregateBy());
|
|
62
|
+
expect(query).toContain("MetricItemAggMV1m");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("skips the MVs and uses count-weighted expressions when the metric is a distribution (histogram) metric", () => {
|
|
66
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy();
|
|
67
|
+
(
|
|
68
|
+
service as unknown as {
|
|
69
|
+
pointTypeHintByAggregate: WeakMap<AggregateBy<Metric>, string | null>;
|
|
70
|
+
}
|
|
71
|
+
).pointTypeHintByAggregate.set(aggregateBy, "Histogram");
|
|
72
|
+
|
|
73
|
+
const query: string = getQuery(aggregateBy);
|
|
74
|
+
|
|
75
|
+
expect(query).not.toContain("MetricItemAggMV1m");
|
|
76
|
+
/*
|
|
77
|
+
* Avg must be count-weighted (sum of observation totals divided
|
|
78
|
+
* by sum of observation counts) — NOT avg(value), which for
|
|
79
|
+
* histogram rows averages per-export-interval sums.
|
|
80
|
+
*/
|
|
81
|
+
expect(query).toContain("sum(multiIf(isNotNull(count)");
|
|
82
|
+
expect(query).toContain("toFloat64(count)");
|
|
83
|
+
expect(query).not.toContain("avg(value)");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("uses count-weighted expressions on the base table when an attribute filter blocks the MVs", () => {
|
|
87
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy({
|
|
88
|
+
query: {
|
|
89
|
+
projectId: projectId,
|
|
90
|
+
name: "http.client.request.duration",
|
|
91
|
+
time: new InBetween(startDate, endDate),
|
|
92
|
+
attributes: { "oneuptime.service.name": "starship-web" },
|
|
93
|
+
} as unknown as AggregateBy<Metric>["query"],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const query: string = getQuery(aggregateBy);
|
|
97
|
+
|
|
98
|
+
expect(query).not.toContain("MetricItemAggMV1m");
|
|
99
|
+
expect(query).toContain("sum(multiIf(isNotNull(count)");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("groups by individual attribute keys, not the whole attributes map", () => {
|
|
103
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy({
|
|
104
|
+
groupByAttributeKeys: ["oneuptime.service.name"],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const result: { statement: Statement; columns: Array<string> } =
|
|
108
|
+
service.toAggregateStatement(aggregateBy);
|
|
109
|
+
const query: string = result.statement.query;
|
|
110
|
+
|
|
111
|
+
// Key extraction is parameter-bound and aliased...
|
|
112
|
+
expect(query).toMatch(/attributes\[\{p\d+:String\}\] AS __attr_grp_0/);
|
|
113
|
+
// ...regrouped rows re-project the selected keys as a compact map...
|
|
114
|
+
expect(query).toMatch(
|
|
115
|
+
/map\(\{p\d+:String\}, __attr_grp_0\) AS attributes/,
|
|
116
|
+
);
|
|
117
|
+
// ...grouping happens on the extracted key, never the Map column.
|
|
118
|
+
expect(query).toContain("GROUP BY time, __attr_grp_0");
|
|
119
|
+
expect(query).not.toContain("GROUP BY time, attributes");
|
|
120
|
+
|
|
121
|
+
// The selected key reaches the query only as a bound parameter.
|
|
122
|
+
const parameterValues: Array<unknown> = Object.values(
|
|
123
|
+
result.statement.query_params,
|
|
124
|
+
);
|
|
125
|
+
expect(
|
|
126
|
+
parameterValues.filter((value: unknown) => {
|
|
127
|
+
return value === "oneuptime.service.name";
|
|
128
|
+
}),
|
|
129
|
+
).toHaveLength(2); // extraction + map key
|
|
130
|
+
|
|
131
|
+
expect(result.columns).toContain("attributes");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("drops a legacy whole-map attributes group-by when key-scoped grouping is requested", () => {
|
|
135
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy({
|
|
136
|
+
groupBy: { attributes: true } as AggregateBy<Metric>["groupBy"],
|
|
137
|
+
groupByAttributeKeys: ["oneuptime.service.name"],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const query: string = getQuery(aggregateBy);
|
|
141
|
+
|
|
142
|
+
expect(query).toContain("GROUP BY time, __attr_grp_0");
|
|
143
|
+
expect(query).not.toContain("GROUP BY time, attributes");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("rejects more than 10 group-by attribute keys", () => {
|
|
147
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy({
|
|
148
|
+
groupByAttributeKeys: Array.from(
|
|
149
|
+
{ length: 11 },
|
|
150
|
+
(_: unknown, i: number) => {
|
|
151
|
+
return `key.${i}`;
|
|
152
|
+
},
|
|
153
|
+
),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(() => {
|
|
157
|
+
return service.toAggregateStatement(aggregateBy);
|
|
158
|
+
}).toThrow(BadDataException);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("percentile aggregations", () => {
|
|
163
|
+
it("keeps the histogram bucket fanout and pools by selected attribute keys", () => {
|
|
164
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy({
|
|
165
|
+
aggregationType: AggregationType.P90,
|
|
166
|
+
groupByAttributeKeys: ["oneuptime.service.name"],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const result: { statement: Statement; columns: Array<string> } =
|
|
170
|
+
service.toAggregateStatement(aggregateBy);
|
|
171
|
+
const query: string = result.statement.query;
|
|
172
|
+
|
|
173
|
+
expect(query).toContain("quantileExactWeighted(0.9)");
|
|
174
|
+
expect(query).toMatch(/attributes\[\{p\d+:String\}\] AS __attr_grp_0/);
|
|
175
|
+
expect(query).toMatch(
|
|
176
|
+
/map\(\{p\d+:String\}, __attr_grp_0\) AS attributes/,
|
|
177
|
+
);
|
|
178
|
+
expect(query).toContain("GROUP BY time, __attr_grp_0");
|
|
179
|
+
expect(result.columns).toContain("attributes");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("does not group when no attribute keys are selected (regression: whole-map fragmentation)", () => {
|
|
183
|
+
const aggregateBy: AggregateBy<Metric> = buildAggregateBy({
|
|
184
|
+
aggregationType: AggregationType.P90,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const query: string = getQuery(aggregateBy);
|
|
188
|
+
|
|
189
|
+
expect(query).toContain("quantileExactWeighted(0.9)");
|
|
190
|
+
expect(query).not.toContain("__attr_grp_0");
|
|
191
|
+
expect(query).toContain("GROUP BY time");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import AIAgentTaskStatus, {
|
|
2
|
+
AIAgentTaskStatusHelper,
|
|
3
|
+
AIAgentTaskStatusProps,
|
|
4
|
+
} from "../../../Types/AI/AIAgentTaskStatus";
|
|
5
|
+
|
|
6
|
+
describe("AIAgentTaskStatusHelper", () => {
|
|
7
|
+
describe("getAllStatusProps", () => {
|
|
8
|
+
test("returns one entry per status with a title and description", () => {
|
|
9
|
+
const props: Array<AIAgentTaskStatusProps> =
|
|
10
|
+
AIAgentTaskStatusHelper.getAllStatusProps();
|
|
11
|
+
|
|
12
|
+
const statuses: Array<AIAgentTaskStatus> =
|
|
13
|
+
Object.values(AIAgentTaskStatus);
|
|
14
|
+
expect(props.length).toBe(statuses.length);
|
|
15
|
+
|
|
16
|
+
for (const prop of props) {
|
|
17
|
+
expect(typeof prop.title).toBe("string");
|
|
18
|
+
expect(prop.title.length).toBeGreaterThan(0);
|
|
19
|
+
expect(typeof prop.description).toBe("string");
|
|
20
|
+
expect(prop.description.length).toBeGreaterThan(0);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("covers every status in the enum exactly once", () => {
|
|
25
|
+
const seen: Set<AIAgentTaskStatus> = new Set<AIAgentTaskStatus>();
|
|
26
|
+
for (const prop of AIAgentTaskStatusHelper.getAllStatusProps()) {
|
|
27
|
+
expect(seen.has(prop.status)).toBe(false);
|
|
28
|
+
seen.add(prop.status);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const status of Object.values(AIAgentTaskStatus)) {
|
|
32
|
+
expect(seen.has(status)).toBe(true);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("getTitle", () => {
|
|
38
|
+
test("returns the configured title for a known status", () => {
|
|
39
|
+
expect(
|
|
40
|
+
AIAgentTaskStatusHelper.getTitle(AIAgentTaskStatus.InProgress),
|
|
41
|
+
).toBe("In Progress");
|
|
42
|
+
expect(
|
|
43
|
+
AIAgentTaskStatusHelper.getTitle(AIAgentTaskStatus.Completed),
|
|
44
|
+
).toBe("Completed");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("returns an empty string for an unknown status", () => {
|
|
48
|
+
expect(
|
|
49
|
+
AIAgentTaskStatusHelper.getTitle("NonExistent" as AIAgentTaskStatus),
|
|
50
|
+
).toBe("");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("getDescription", () => {
|
|
55
|
+
test("returns a non-empty description for a known status", () => {
|
|
56
|
+
expect(
|
|
57
|
+
AIAgentTaskStatusHelper.getDescription(AIAgentTaskStatus.Scheduled)
|
|
58
|
+
.length,
|
|
59
|
+
).toBeGreaterThan(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("returns an empty string for an unknown status", () => {
|
|
63
|
+
expect(
|
|
64
|
+
AIAgentTaskStatusHelper.getDescription(
|
|
65
|
+
"NonExistent" as AIAgentTaskStatus,
|
|
66
|
+
),
|
|
67
|
+
).toBe("");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("isTerminalStatus", () => {
|
|
72
|
+
test.each([AIAgentTaskStatus.Completed, AIAgentTaskStatus.Error])(
|
|
73
|
+
"returns true for terminal status %s",
|
|
74
|
+
(status: AIAgentTaskStatus) => {
|
|
75
|
+
expect(AIAgentTaskStatusHelper.isTerminalStatus(status)).toBe(true);
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
test.each([AIAgentTaskStatus.Scheduled, AIAgentTaskStatus.InProgress])(
|
|
80
|
+
"returns false for non-terminal status %s",
|
|
81
|
+
(status: AIAgentTaskStatus) => {
|
|
82
|
+
expect(AIAgentTaskStatusHelper.isTerminalStatus(status)).toBe(false);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
});
|