@oneuptime/common 12.0.14 → 12.0.15
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/GlobalOidc.ts +20 -0
- package/Models/DatabaseModels/GlobalSso.ts +20 -0
- package/Models/DatabaseModels/MarketingConversion.ts +19 -9
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
- package/Models/DatabaseModels/StatusPage.ts +7 -4
- package/Server/API/StatusPageAPI.ts +29 -4
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/SlackAuthorization.ts +43 -6
- package/Server/Middleware/UserAuthorization.ts +236 -26
- package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
- package/Server/Services/GlobalOidcProjectService.ts +126 -1
- package/Server/Services/GlobalOidcService.ts +132 -0
- package/Server/Services/GlobalSsoProjectService.ts +126 -1
- package/Server/Services/GlobalSsoService.ts +132 -0
- package/Server/Services/UserEmailService.ts +4 -4
- package/Server/Utils/CronTab.ts +73 -0
- package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
- package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
- package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
- package/Server/Utils/Monitor/MonitorResource.ts +161 -21
- package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
- package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
- package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
- package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
- package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
- package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
- package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
- package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
- package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
- package/Tests/Server/Utils/CronTab.test.ts +116 -0
- package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
- package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
- package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
- package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
- package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
- package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
- package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
- package/Tests/Utils/Analytics.test.ts +187 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
- package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
- package/Types/Marketing/MarketingConversion.ts +12 -0
- package/Types/Monitor/CriteriaFilter.ts +9 -0
- package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
- package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
- package/UI/Components/Forms/BasicForm.tsx +13 -1
- package/UI/Components/Forms/BasicModelForm.tsx +2 -1
- package/UI/Components/Forms/ModelForm.tsx +10 -1
- package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
- package/Utils/Analytics.ts +55 -2
- package/Utils/Monitor/MonitorMetricType.ts +40 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
- package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +25 -4
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcService.js +127 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoService.js +127 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +4 -4
- package/build/dist/Server/Utils/CronTab.js +66 -0
- package/build/dist/Server/Utils/CronTab.js.map +1 -1
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
- package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/Utils/Analytics.js +50 -2
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import APIRequestCriteria from "../../../../../Server/Utils/Monitor/Criteria/APIRequestCriteria";
|
|
2
|
+
import MetricService from "../../../../../Server/Services/MetricService";
|
|
3
|
+
import FindBy from "../../../../../Server/Types/AnalyticsDatabase/FindBy";
|
|
4
|
+
import Metric from "../../../../../Models/AnalyticsModels/Metric";
|
|
5
|
+
import {
|
|
6
|
+
CheckOn,
|
|
7
|
+
CriteriaFilter,
|
|
8
|
+
EvaluateOverTimeType,
|
|
9
|
+
FilterType,
|
|
10
|
+
NoDataPolicy,
|
|
11
|
+
} from "../../../../../Types/Monitor/CriteriaFilter";
|
|
12
|
+
import ProbeMonitorResponse from "../../../../../Types/Probe/ProbeMonitorResponse";
|
|
13
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
14
|
+
import {
|
|
15
|
+
afterEach,
|
|
16
|
+
beforeEach,
|
|
17
|
+
describe,
|
|
18
|
+
expect,
|
|
19
|
+
jest,
|
|
20
|
+
test,
|
|
21
|
+
} from "@jest/globals";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* End-to-end cover for the bug in
|
|
25
|
+
* https://github.com/OneUptime/oneuptime/issues/2321: a criteria filter set
|
|
26
|
+
* to look at "All Values over the last N minutes" fired on the first bad
|
|
27
|
+
* probe instead of waiting for the window.
|
|
28
|
+
*
|
|
29
|
+
* These tests drive the real APIRequestCriteria evaluator and stub only the
|
|
30
|
+
* metric read, so the window arithmetic, the coverage guard, the no-data
|
|
31
|
+
* policy and the comparison message are all exercised together.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const NOW: Date = new Date("2026-08-20T12:00:00.000Z");
|
|
35
|
+
|
|
36
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
37
|
+
"11111111-1111-4111-8111-111111111111",
|
|
38
|
+
);
|
|
39
|
+
const MONITOR_ID: ObjectID = new ObjectID(
|
|
40
|
+
"22222222-2222-4222-8222-222222222222",
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/** Once a minute, oldest first, ending at `now`. */
|
|
44
|
+
function everyMinute(values: Array<number>): Array<Metric> {
|
|
45
|
+
return values.map((value: number, index: number) => {
|
|
46
|
+
const metric: Metric = new Metric();
|
|
47
|
+
metric.value = value;
|
|
48
|
+
metric.time = new Date(
|
|
49
|
+
NOW.getTime() - (values.length - 1 - index) * 60 * 1000,
|
|
50
|
+
);
|
|
51
|
+
return metric;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildResponse(
|
|
56
|
+
overrides: Partial<ProbeMonitorResponse> = {},
|
|
57
|
+
): ProbeMonitorResponse {
|
|
58
|
+
return {
|
|
59
|
+
projectId: PROJECT_ID,
|
|
60
|
+
monitorId: MONITOR_ID,
|
|
61
|
+
monitorStepId: ObjectID.generate(),
|
|
62
|
+
probeId: ObjectID.generate(),
|
|
63
|
+
failureCause: "",
|
|
64
|
+
monitoredAt: NOW,
|
|
65
|
+
isOnline: true,
|
|
66
|
+
responseCode: 200,
|
|
67
|
+
responseTimeInMs: 100,
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let windowSamples: Array<Metric> = [];
|
|
73
|
+
|
|
74
|
+
function evaluate(input: {
|
|
75
|
+
criteriaFilter: CriteriaFilter;
|
|
76
|
+
dataToProcess: ProbeMonitorResponse;
|
|
77
|
+
monitoringInterval?: string | undefined;
|
|
78
|
+
}): Promise<string | null> {
|
|
79
|
+
return APIRequestCriteria.isMonitorInstanceCriteriaFilterMet({
|
|
80
|
+
dataToProcess: input.dataToProcess,
|
|
81
|
+
criteriaFilter: input.criteriaFilter,
|
|
82
|
+
monitoringInterval: input.monitoringInterval ?? "* * * * *",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe("APIRequestCriteria evaluate over time", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
jest.useFakeTimers();
|
|
89
|
+
jest.setSystemTime(NOW);
|
|
90
|
+
|
|
91
|
+
windowSamples = [];
|
|
92
|
+
|
|
93
|
+
jest
|
|
94
|
+
.spyOn(MetricService, "findBy")
|
|
95
|
+
.mockImplementation((_findBy: FindBy<Metric>): Promise<Array<Metric>> => {
|
|
96
|
+
return Promise.resolve(windowSamples);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
jest.useRealTimers();
|
|
102
|
+
jest.restoreAllMocks();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("the reported scenario: one bad status code in a healthy window", () => {
|
|
106
|
+
const offlineCriteria: CriteriaFilter = {
|
|
107
|
+
checkOn: CheckOn.ResponseStatusCode,
|
|
108
|
+
filterType: FilterType.NotEqualTo,
|
|
109
|
+
value: 200,
|
|
110
|
+
evaluateOverTime: true,
|
|
111
|
+
evaluateOverTimeOptions: {
|
|
112
|
+
timeValueInMinutes: 5,
|
|
113
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
test("does not fire when only the newest sample is bad", async () => {
|
|
118
|
+
windowSamples = everyMinute([200, 200, 200, 200, 404]);
|
|
119
|
+
|
|
120
|
+
const result: string | null = await evaluate({
|
|
121
|
+
criteriaFilter: offlineCriteria,
|
|
122
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(result).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
* The heart of the bug. Nothing had been recorded yet, so the evaluator
|
|
130
|
+
* used to compare the single status code that arrived with this check
|
|
131
|
+
* and report it as "all values over the last 5 minutes".
|
|
132
|
+
*/
|
|
133
|
+
test("does not fire when the window is empty", async () => {
|
|
134
|
+
windowSamples = [];
|
|
135
|
+
|
|
136
|
+
const result: string | null = await evaluate({
|
|
137
|
+
criteriaFilter: offlineCriteria,
|
|
138
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("does not fire on a monitor that has only checked once", async () => {
|
|
145
|
+
windowSamples = everyMinute([404]);
|
|
146
|
+
|
|
147
|
+
const result: string | null = await evaluate({
|
|
148
|
+
criteriaFilter: offlineCriteria,
|
|
149
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("does not fire two minutes into a five minute window", async () => {
|
|
156
|
+
windowSamples = everyMinute([404, 404]);
|
|
157
|
+
|
|
158
|
+
const result: string | null = await evaluate({
|
|
159
|
+
criteriaFilter: offlineCriteria,
|
|
160
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(result).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("fires once the whole window has been bad", async () => {
|
|
167
|
+
windowSamples = everyMinute([404, 404, 404, 404, 404]);
|
|
168
|
+
|
|
169
|
+
const result: string | null = await evaluate({
|
|
170
|
+
criteriaFilter: offlineCriteria,
|
|
171
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(result).toContain("All values of");
|
|
175
|
+
expect(result).toContain(CheckOn.ResponseStatusCode as string);
|
|
176
|
+
expect(result).toContain("over the last 5 minutes");
|
|
177
|
+
expect(result).toContain("not equal to 200");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* The recovery direction matters too: a single good sample inside an
|
|
182
|
+
* otherwise bad window must clear the criteria.
|
|
183
|
+
*/
|
|
184
|
+
test("stops firing as soon as one sample recovers", async () => {
|
|
185
|
+
windowSamples = everyMinute([404, 404, 404, 404, 200]);
|
|
186
|
+
|
|
187
|
+
const result: string | null = await evaluate({
|
|
188
|
+
criteriaFilter: offlineCriteria,
|
|
189
|
+
dataToProcess: buildResponse({ responseCode: 200 }),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(result).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("the reported scenario: a single latency spike", () => {
|
|
197
|
+
/*
|
|
198
|
+
* From the second report on the issue - one 9171 ms tick inside a two
|
|
199
|
+
* minute window of sub-second responses opened an incident that
|
|
200
|
+
* auto-resolved 72 seconds later.
|
|
201
|
+
*/
|
|
202
|
+
const slowCriteria: CriteriaFilter = {
|
|
203
|
+
checkOn: CheckOn.ResponseTime,
|
|
204
|
+
filterType: FilterType.GreaterThan,
|
|
205
|
+
value: 8000,
|
|
206
|
+
evaluateOverTime: true,
|
|
207
|
+
evaluateOverTimeOptions: {
|
|
208
|
+
timeValueInMinutes: 2,
|
|
209
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
test("does not fire on a lone spike among healthy samples", async () => {
|
|
214
|
+
windowSamples = everyMinute([397, 805, 9171]);
|
|
215
|
+
|
|
216
|
+
const result: string | null = await evaluate({
|
|
217
|
+
criteriaFilter: slowCriteria,
|
|
218
|
+
dataToProcess: buildResponse({ responseTimeInMs: 9171 }),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(result).toBeNull();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("fires when the whole window is slow", async () => {
|
|
225
|
+
windowSamples = everyMinute([9171, 9002, 8800]);
|
|
226
|
+
|
|
227
|
+
const result: string | null = await evaluate({
|
|
228
|
+
criteriaFilter: slowCriteria,
|
|
229
|
+
dataToProcess: buildResponse({ responseTimeInMs: 8800 }),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(result).toContain("All values of");
|
|
233
|
+
expect(result).toContain("greater than 8000");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/*
|
|
237
|
+
* "Any Value" is the setting for "tell me the moment one check
|
|
238
|
+
* breaches", and it must keep behaving that way.
|
|
239
|
+
*/
|
|
240
|
+
test("Any Value still fires on the lone spike", async () => {
|
|
241
|
+
windowSamples = everyMinute([397, 805, 9171]);
|
|
242
|
+
|
|
243
|
+
const result: string | null = await evaluate({
|
|
244
|
+
criteriaFilter: {
|
|
245
|
+
...slowCriteria,
|
|
246
|
+
evaluateOverTimeOptions: {
|
|
247
|
+
timeValueInMinutes: 2,
|
|
248
|
+
evaluateOverTimeType: EvaluateOverTimeType.AnyValue,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
dataToProcess: buildResponse({ responseTimeInMs: 9171 }),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(result).toContain("Any value of");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("Any Value fires off the very first sample", async () => {
|
|
258
|
+
windowSamples = everyMinute([9171]);
|
|
259
|
+
|
|
260
|
+
const result: string | null = await evaluate({
|
|
261
|
+
criteriaFilter: {
|
|
262
|
+
...slowCriteria,
|
|
263
|
+
evaluateOverTimeOptions: {
|
|
264
|
+
timeValueInMinutes: 2,
|
|
265
|
+
evaluateOverTimeType: EvaluateOverTimeType.AnyValue,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
dataToProcess: buildResponse({ responseTimeInMs: 9171 }),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(result).not.toBeNull();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("Is Online over time", () => {
|
|
276
|
+
const offlineCriteria: CriteriaFilter = {
|
|
277
|
+
checkOn: CheckOn.IsOnline,
|
|
278
|
+
filterType: FilterType.False,
|
|
279
|
+
value: undefined,
|
|
280
|
+
evaluateOverTime: true,
|
|
281
|
+
evaluateOverTimeOptions: {
|
|
282
|
+
timeValueInMinutes: 5,
|
|
283
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
test("fires only after the whole window has been offline", async () => {
|
|
288
|
+
windowSamples = everyMinute([0, 0, 0, 0, 0]);
|
|
289
|
+
|
|
290
|
+
const result: string | null = await evaluate({
|
|
291
|
+
criteriaFilter: offlineCriteria,
|
|
292
|
+
dataToProcess: buildResponse({ isOnline: false }),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
expect(result).toContain(CheckOn.IsOnline as string);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("does not fire on the first failed check", async () => {
|
|
299
|
+
windowSamples = everyMinute([1, 1, 1, 1, 0]);
|
|
300
|
+
|
|
301
|
+
const result: string | null = await evaluate({
|
|
302
|
+
criteriaFilter: offlineCriteria,
|
|
303
|
+
dataToProcess: buildResponse({ isOnline: false }),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(result).toBeNull();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("does not fire while the window is still filling", async () => {
|
|
310
|
+
windowSamples = everyMinute([0]);
|
|
311
|
+
|
|
312
|
+
const result: string | null = await evaluate({
|
|
313
|
+
criteriaFilter: offlineCriteria,
|
|
314
|
+
dataToProcess: buildResponse({ isOnline: false }),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(result).toBeNull();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("no-data policy", () => {
|
|
322
|
+
const criteriaWith: (policy: NoDataPolicy) => CriteriaFilter = (
|
|
323
|
+
policy: NoDataPolicy,
|
|
324
|
+
): CriteriaFilter => {
|
|
325
|
+
return {
|
|
326
|
+
checkOn: CheckOn.ResponseStatusCode,
|
|
327
|
+
filterType: FilterType.NotEqualTo,
|
|
328
|
+
value: 200,
|
|
329
|
+
evaluateOverTime: true,
|
|
330
|
+
evaluateOverTimeOptions: {
|
|
331
|
+
timeValueInMinutes: 5,
|
|
332
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
333
|
+
onNoDataPolicy: policy,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
test("Ignore keeps quiet when there is nothing to look at", async () => {
|
|
339
|
+
windowSamples = [];
|
|
340
|
+
|
|
341
|
+
const result: string | null = await evaluate({
|
|
342
|
+
criteriaFilter: criteriaWith(NoDataPolicy.Ignore),
|
|
343
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(result).toBeNull();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
/*
|
|
350
|
+
* For heartbeat-style checks the absence of data is itself the problem,
|
|
351
|
+
* which is what Trigger is for.
|
|
352
|
+
*/
|
|
353
|
+
test("Trigger reports the missing data as the root cause", async () => {
|
|
354
|
+
windowSamples = [];
|
|
355
|
+
|
|
356
|
+
const result: string | null = await evaluate({
|
|
357
|
+
criteriaFilter: criteriaWith(NoDataPolicy.Trigger),
|
|
358
|
+
dataToProcess: buildResponse({ responseCode: 200 }),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(result).toContain("no data");
|
|
362
|
+
expect(result).toContain("5 minutes");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("Treat As Zero compares the window as a single zero", async () => {
|
|
366
|
+
windowSamples = [];
|
|
367
|
+
|
|
368
|
+
const result: string | null = await evaluate({
|
|
369
|
+
criteriaFilter: criteriaWith(NoDataPolicy.TreatAsZero),
|
|
370
|
+
dataToProcess: buildResponse({ responseCode: 200 }),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// 0 is not equal to 200, so the filter is met.
|
|
374
|
+
expect(result).toContain("not equal to 200");
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe("monitors that are polled less often than the window", () => {
|
|
379
|
+
/*
|
|
380
|
+
* A monitor checked every five minutes can only ever put one sample into
|
|
381
|
+
* a five minute window, so that sample IS the whole window. Requiring
|
|
382
|
+
* more would mean it never fires at all.
|
|
383
|
+
*/
|
|
384
|
+
test("a single sample covers the window at a five minute interval", async () => {
|
|
385
|
+
windowSamples = everyMinute([404]);
|
|
386
|
+
|
|
387
|
+
const result: string | null = await evaluate({
|
|
388
|
+
criteriaFilter: {
|
|
389
|
+
checkOn: CheckOn.ResponseStatusCode,
|
|
390
|
+
filterType: FilterType.NotEqualTo,
|
|
391
|
+
value: 200,
|
|
392
|
+
evaluateOverTime: true,
|
|
393
|
+
evaluateOverTimeOptions: {
|
|
394
|
+
timeValueInMinutes: 5,
|
|
395
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
399
|
+
monitoringInterval: "*/5 * * * *",
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(result).not.toBeNull();
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe("attribute scoping", () => {
|
|
407
|
+
/*
|
|
408
|
+
* Only the disk-usage series carries a diskPath attribute. A stray
|
|
409
|
+
* serverMonitorOptions left on a non-disk filter used to be harmless
|
|
410
|
+
* because an empty window fell back to the live value; now it would
|
|
411
|
+
* silence the filter, so it must not reach the query.
|
|
412
|
+
*/
|
|
413
|
+
test("does not scope a response-time window by a stray disk path", async () => {
|
|
414
|
+
windowSamples = everyMinute([9000, 9000, 9000]);
|
|
415
|
+
|
|
416
|
+
const findBySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
|
|
417
|
+
MetricService,
|
|
418
|
+
"findBy",
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const result: string | null = await evaluate({
|
|
422
|
+
criteriaFilter: {
|
|
423
|
+
checkOn: CheckOn.ResponseTime,
|
|
424
|
+
filterType: FilterType.GreaterThan,
|
|
425
|
+
value: 8000,
|
|
426
|
+
evaluateOverTime: true,
|
|
427
|
+
evaluateOverTimeOptions: {
|
|
428
|
+
timeValueInMinutes: 2,
|
|
429
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
430
|
+
},
|
|
431
|
+
serverMonitorOptions: { diskPath: "/" },
|
|
432
|
+
},
|
|
433
|
+
dataToProcess: buildResponse({ responseTimeInMs: 9000 }),
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const query: Record<string, unknown> = (
|
|
437
|
+
findBySpy.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
438
|
+
).query;
|
|
439
|
+
|
|
440
|
+
expect(query["attributes"]).toBeUndefined();
|
|
441
|
+
expect(result).not.toBeNull();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe("filters that do not evaluate over time", () => {
|
|
446
|
+
test("still compare the value that arrived with this check", async () => {
|
|
447
|
+
windowSamples = [];
|
|
448
|
+
|
|
449
|
+
const result: string | null = await evaluate({
|
|
450
|
+
criteriaFilter: {
|
|
451
|
+
checkOn: CheckOn.ResponseStatusCode,
|
|
452
|
+
filterType: FilterType.NotEqualTo,
|
|
453
|
+
value: 200,
|
|
454
|
+
evaluateOverTime: false,
|
|
455
|
+
},
|
|
456
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(result).toContain("not equal to 200");
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("the metric store is never read for them", async () => {
|
|
463
|
+
const findBySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
|
|
464
|
+
MetricService,
|
|
465
|
+
"findBy",
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
await evaluate({
|
|
469
|
+
criteriaFilter: {
|
|
470
|
+
checkOn: CheckOn.ResponseStatusCode,
|
|
471
|
+
filterType: FilterType.NotEqualTo,
|
|
472
|
+
value: 200,
|
|
473
|
+
evaluateOverTime: false,
|
|
474
|
+
},
|
|
475
|
+
dataToProcess: buildResponse({ responseCode: 404 }),
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe("checks with no metric series of their own", () => {
|
|
483
|
+
/*
|
|
484
|
+
* Response Body is not recorded as a metric, so an over-time flag on it
|
|
485
|
+
* cannot be honoured. It falls back to the body from this check rather
|
|
486
|
+
* than silently never matching.
|
|
487
|
+
*/
|
|
488
|
+
test("fall back to the value from this check", async () => {
|
|
489
|
+
windowSamples = [];
|
|
490
|
+
|
|
491
|
+
const result: string | null = await evaluate({
|
|
492
|
+
criteriaFilter: {
|
|
493
|
+
checkOn: CheckOn.ResponseBody,
|
|
494
|
+
filterType: FilterType.Contains,
|
|
495
|
+
value: "healthy",
|
|
496
|
+
evaluateOverTime: true,
|
|
497
|
+
evaluateOverTimeOptions: {
|
|
498
|
+
timeValueInMinutes: 5,
|
|
499
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
dataToProcess: buildResponse({ responseBody: "all healthy here" }),
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
expect(result).toContain("Response body contains healthy");
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import APIRequestCriteria from "../../../../../Server/Utils/Monitor/Criteria/APIRequestCriteria";
|
|
2
|
-
import EvaluateOverTime
|
|
2
|
+
import EvaluateOverTime, {
|
|
3
|
+
OverTimeCriteriaValue,
|
|
4
|
+
} from "../../../../../Server/Utils/Monitor/Criteria/EvaluateOverTime";
|
|
3
5
|
import {
|
|
4
6
|
CheckOn,
|
|
5
7
|
CriteriaFilter,
|
|
@@ -142,8 +144,11 @@ describe("APIRequestCriteria Port connection phase timings", () => {
|
|
|
142
144
|
|
|
143
145
|
test("uses an evaluate-over-time aggregate instead of the current phase", async () => {
|
|
144
146
|
const overTimeSpy: ReturnType<typeof jest.spyOn> = jest
|
|
145
|
-
.spyOn(EvaluateOverTime, "
|
|
146
|
-
.mockResolvedValue(
|
|
147
|
+
.spyOn(EvaluateOverTime, "getOverTimeValueForCriteriaFilter")
|
|
148
|
+
.mockResolvedValue({
|
|
149
|
+
earlyReturn: null,
|
|
150
|
+
value: 250,
|
|
151
|
+
} as OverTimeCriteriaValue);
|
|
147
152
|
|
|
148
153
|
const response: ProbeMonitorResponse = buildResponse({
|
|
149
154
|
dnsLookupInMs: 5,
|
|
@@ -164,13 +169,20 @@ describe("APIRequestCriteria Port connection phase timings", () => {
|
|
|
164
169
|
expect.objectContaining({
|
|
165
170
|
projectId: response.projectId,
|
|
166
171
|
monitorId: response.monitorId,
|
|
167
|
-
|
|
172
|
+
criteriaFilter: expect.objectContaining({
|
|
173
|
+
checkOn: CheckOn.PortDnsLookupTime,
|
|
174
|
+
}),
|
|
168
175
|
}),
|
|
169
176
|
);
|
|
170
177
|
});
|
|
171
178
|
|
|
172
179
|
test("does not replace a zero evaluate-over-time aggregate with the current value", async () => {
|
|
173
|
-
jest
|
|
180
|
+
jest
|
|
181
|
+
.spyOn(EvaluateOverTime, "getOverTimeValueForCriteriaFilter")
|
|
182
|
+
.mockResolvedValue({
|
|
183
|
+
earlyReturn: null,
|
|
184
|
+
value: 0,
|
|
185
|
+
} as OverTimeCriteriaValue);
|
|
174
186
|
|
|
175
187
|
const result: string | null = await evaluate(
|
|
176
188
|
buildResponse({
|
|
@@ -189,10 +201,19 @@ describe("APIRequestCriteria Port connection phase timings", () => {
|
|
|
189
201
|
expect(result).not.toBeNull();
|
|
190
202
|
});
|
|
191
203
|
|
|
192
|
-
|
|
204
|
+
/*
|
|
205
|
+
* An over-time filter whose history could not be read says nothing about
|
|
206
|
+
* the last N minutes, so it must not fire. It used to quietly compare the
|
|
207
|
+
* single phase timing from this one check instead - the same fallback that
|
|
208
|
+
* let "all values over the last N minutes" fire off one bad probe.
|
|
209
|
+
*/
|
|
210
|
+
test("does not fire off the current phase if the history query fails", async () => {
|
|
193
211
|
jest
|
|
194
|
-
.spyOn(EvaluateOverTime, "
|
|
195
|
-
.
|
|
212
|
+
.spyOn(EvaluateOverTime, "getOverTimeValueForCriteriaFilter")
|
|
213
|
+
.mockResolvedValue({
|
|
214
|
+
earlyReturn: { result: null },
|
|
215
|
+
value: undefined,
|
|
216
|
+
} as OverTimeCriteriaValue);
|
|
196
217
|
|
|
197
218
|
const result: string | null = await evaluate(
|
|
198
219
|
buildResponse({
|
|
@@ -207,6 +228,6 @@ describe("APIRequestCriteria Port connection phase timings", () => {
|
|
|
207
228
|
}),
|
|
208
229
|
);
|
|
209
230
|
|
|
210
|
-
expect(result).
|
|
231
|
+
expect(result).toBeNull();
|
|
211
232
|
});
|
|
212
233
|
});
|