@oneuptime/common 12.0.3 → 12.0.4
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/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/DashboardAPI.ts +419 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +7 -3
- package/Server/Infrastructure/GlobalCache.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +185 -2
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/PasswordHash.ts +306 -0
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
- package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
- package/Types/Database/ColumnLength.ts +9 -1
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/HashedString.ts +139 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +304 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +7 -3
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +160 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- package/build/dist/Server/Utils/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- package/build/dist/Types/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- package/build/dist/Utils/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IoTAlertTemplate,
|
|
3
|
+
IoTAlertTemplateArgs,
|
|
4
|
+
IoTAlertTemplateCategory,
|
|
5
|
+
getAllIoTAlertTemplates,
|
|
6
|
+
getIoTAlertTemplateById,
|
|
7
|
+
getIoTAlertTemplatesByCategory,
|
|
8
|
+
} from "../../../Types/Monitor/IotAlertTemplates";
|
|
9
|
+
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
10
|
+
import MonitorStepIoTMonitor from "../../../Types/Monitor/MonitorStepIoTMonitor";
|
|
11
|
+
import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
|
|
12
|
+
import {
|
|
13
|
+
FilterType,
|
|
14
|
+
NoDataPolicy,
|
|
15
|
+
} from "../../../Types/Monitor/CriteriaFilter";
|
|
16
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* The IoT templates carry two decisions the container templates don't, and
|
|
20
|
+
* both are the point of this suite:
|
|
21
|
+
*
|
|
22
|
+
* 1. INVERTED thresholds. "Bad" for a device is a value that is too LOW —
|
|
23
|
+
* no heartbeat (iot_device_up < 1), a draining battery (< 20%), a fading
|
|
24
|
+
* radio (< -100 dBm). So the unhealthy criterion is `<` and the healthy
|
|
25
|
+
* one is `>=`, the opposite direction from a CPU/memory ceiling. A
|
|
26
|
+
* copy-paste of a `>`/`<=` pair from the ceiling templates would make the
|
|
27
|
+
* monitor alert exactly when the device is fine. Temperature and CPU are
|
|
28
|
+
* genuine ceilings and keep the `>`/`<=` direction — the mix is what makes
|
|
29
|
+
* pinning each direction per template worthwhile.
|
|
30
|
+
*
|
|
31
|
+
* 2. Per-device grouping. Every template groups by the `device.id` datapoint
|
|
32
|
+
* label (NOT `resource.`-prefixed — it is a datapoint attribute, not a
|
|
33
|
+
* resource attribute) so one incident fires per device rather than one
|
|
34
|
+
* collapsed series for the whole fleet.
|
|
35
|
+
*
|
|
36
|
+
* 3. The Device Offline template — and ONLY it — opts into TreatAsZero so a
|
|
37
|
+
* registered device that goes completely silent (an empty series) folds
|
|
38
|
+
* to 0 and trips `Min(iot_device_up) < 1`. Enabling that on a battery or
|
|
39
|
+
* temperature threshold would false-alarm for any silent device, so the
|
|
40
|
+
* other templates must NOT carry it.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
interface IoTTemplateCase {
|
|
44
|
+
id: string;
|
|
45
|
+
category: IoTAlertTemplateCategory;
|
|
46
|
+
severity: "Critical" | "Warning";
|
|
47
|
+
metricName: string;
|
|
48
|
+
metricAlias: string;
|
|
49
|
+
aggregation: MetricsAggregationType;
|
|
50
|
+
offlineFilterType: FilterType;
|
|
51
|
+
onlineFilterType: FilterType;
|
|
52
|
+
threshold: number;
|
|
53
|
+
treatNoDataAsZero: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const IOT_TEMPLATES: Array<IoTTemplateCase> = [
|
|
57
|
+
{
|
|
58
|
+
id: "iot-device-offline",
|
|
59
|
+
category: "Availability",
|
|
60
|
+
severity: "Critical",
|
|
61
|
+
metricName: "iot_device_up",
|
|
62
|
+
metricAlias: "device_up",
|
|
63
|
+
// Min: a single down push in the window must win over healthy pushes.
|
|
64
|
+
aggregation: MetricsAggregationType.Min,
|
|
65
|
+
offlineFilterType: FilterType.LessThan,
|
|
66
|
+
onlineFilterType: FilterType.GreaterThanOrEqualTo,
|
|
67
|
+
threshold: 1,
|
|
68
|
+
treatNoDataAsZero: true,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "iot-low-battery",
|
|
72
|
+
category: "Power",
|
|
73
|
+
severity: "Warning",
|
|
74
|
+
metricName: "iot_battery_percent",
|
|
75
|
+
metricAlias: "battery_percent",
|
|
76
|
+
aggregation: MetricsAggregationType.Avg,
|
|
77
|
+
offlineFilterType: FilterType.LessThan,
|
|
78
|
+
onlineFilterType: FilterType.GreaterThanOrEqualTo,
|
|
79
|
+
threshold: 20,
|
|
80
|
+
treatNoDataAsZero: false,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "iot-weak-signal",
|
|
84
|
+
category: "Connectivity",
|
|
85
|
+
severity: "Warning",
|
|
86
|
+
metricName: "iot_signal_strength_dbm",
|
|
87
|
+
metricAlias: "signal_strength",
|
|
88
|
+
aggregation: MetricsAggregationType.Avg,
|
|
89
|
+
offlineFilterType: FilterType.LessThan,
|
|
90
|
+
onlineFilterType: FilterType.GreaterThanOrEqualTo,
|
|
91
|
+
// dBm is negative; -100 is a real threshold, not a placeholder.
|
|
92
|
+
threshold: -100,
|
|
93
|
+
treatNoDataAsZero: false,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "iot-high-temperature",
|
|
97
|
+
category: "Environment",
|
|
98
|
+
severity: "Critical",
|
|
99
|
+
metricName: "iot_temperature_celsius",
|
|
100
|
+
metricAlias: "temperature_celsius",
|
|
101
|
+
// Max: a single hot reading must win over cooler ones in the minute.
|
|
102
|
+
aggregation: MetricsAggregationType.Max,
|
|
103
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
104
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
105
|
+
threshold: 70,
|
|
106
|
+
treatNoDataAsZero: false,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "iot-high-cpu",
|
|
110
|
+
category: "System",
|
|
111
|
+
severity: "Warning",
|
|
112
|
+
metricName: "iot_cpu_usage_ratio",
|
|
113
|
+
metricAlias: "cpu_usage",
|
|
114
|
+
aggregation: MetricsAggregationType.Avg,
|
|
115
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
116
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
117
|
+
// iot_cpu_usage_ratio is a 0-1 ratio, so 0.9 == 90%.
|
|
118
|
+
threshold: 0.9,
|
|
119
|
+
treatNoDataAsZero: false,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
function buildArgs(): IoTAlertTemplateArgs {
|
|
124
|
+
return {
|
|
125
|
+
fleetIdentifier: "field-fleet-a",
|
|
126
|
+
onlineMonitorStatusId: ObjectID.generate(),
|
|
127
|
+
offlineMonitorStatusId: ObjectID.generate(),
|
|
128
|
+
defaultIncidentSeverityId: ObjectID.generate(),
|
|
129
|
+
defaultAlertSeverityId: ObjectID.generate(),
|
|
130
|
+
monitorName: "Test IoT Monitor",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getIoTMonitor(step: MonitorStep): MonitorStepIoTMonitor {
|
|
135
|
+
const iotMonitor: MonitorStepIoTMonitor | undefined = step.data?.iotMonitor;
|
|
136
|
+
if (!iotMonitor) {
|
|
137
|
+
throw new Error("iotMonitor missing from monitor step");
|
|
138
|
+
}
|
|
139
|
+
return iotMonitor;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
describe("IotAlertTemplates", () => {
|
|
143
|
+
test("every documented template id is registered and the suite is exhaustive", () => {
|
|
144
|
+
const ids: Array<string> = getAllIoTAlertTemplates().map(
|
|
145
|
+
(t: IoTAlertTemplate) => {
|
|
146
|
+
return t.id;
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
for (const tc of IOT_TEMPLATES) {
|
|
150
|
+
expect(ids).toContain(tc.id);
|
|
151
|
+
}
|
|
152
|
+
expect(ids.sort()).toEqual(
|
|
153
|
+
IOT_TEMPLATES.map((t: IoTTemplateCase) => {
|
|
154
|
+
return t.id;
|
|
155
|
+
}).sort(),
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("every template id is unique", () => {
|
|
160
|
+
const ids: Array<string> = getAllIoTAlertTemplates().map(
|
|
161
|
+
(t: IoTAlertTemplate) => {
|
|
162
|
+
return t.id;
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("getIoTAlertTemplateById returns undefined for an unknown id", () => {
|
|
169
|
+
expect(getIoTAlertTemplateById("nope")).toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("getIoTAlertTemplatesByCategory returns only that category and covers the catalog", () => {
|
|
173
|
+
const all: Array<IoTAlertTemplate> = getAllIoTAlertTemplates();
|
|
174
|
+
const categories: Array<IoTAlertTemplateCategory> = [
|
|
175
|
+
"Availability",
|
|
176
|
+
"Power",
|
|
177
|
+
"Connectivity",
|
|
178
|
+
"Environment",
|
|
179
|
+
"System",
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
let total: number = 0;
|
|
183
|
+
for (const category of categories) {
|
|
184
|
+
const inCategory: Array<IoTAlertTemplate> =
|
|
185
|
+
getIoTAlertTemplatesByCategory(category);
|
|
186
|
+
for (const template of inCategory) {
|
|
187
|
+
expect(template.category).toBe(category);
|
|
188
|
+
}
|
|
189
|
+
total += inCategory.length;
|
|
190
|
+
}
|
|
191
|
+
expect(total).toBe(all.length);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test.each(IOT_TEMPLATES)(
|
|
195
|
+
"$id is a $severity $category template with populated copy",
|
|
196
|
+
(tc: IoTTemplateCase) => {
|
|
197
|
+
const template: IoTAlertTemplate | undefined = getIoTAlertTemplateById(
|
|
198
|
+
tc.id,
|
|
199
|
+
);
|
|
200
|
+
expect(template).toBeDefined();
|
|
201
|
+
expect(template!.category).toBe(tc.category);
|
|
202
|
+
expect(template!.severity).toBe(tc.severity);
|
|
203
|
+
expect(template!.name.length).toBeGreaterThan(0);
|
|
204
|
+
expect(template!.description.length).toBeGreaterThan(0);
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
test.each(IOT_TEMPLATES)(
|
|
209
|
+
"$id queries $metricName grouped per device with the intended aggregation",
|
|
210
|
+
(tc: IoTTemplateCase) => {
|
|
211
|
+
const template: IoTAlertTemplate = getIoTAlertTemplateById(tc.id)!;
|
|
212
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
213
|
+
const monitor: MonitorStepIoTMonitor = getIoTMonitor(step);
|
|
214
|
+
|
|
215
|
+
expect(monitor.fleetIdentifier).toBe("field-fleet-a");
|
|
216
|
+
|
|
217
|
+
const queryConfigs: Array<any> = monitor.metricViewConfig
|
|
218
|
+
.queryConfigs as Array<any>;
|
|
219
|
+
expect(queryConfigs).toHaveLength(1);
|
|
220
|
+
expect(monitor.metricViewConfig.formulaConfigs).toHaveLength(0);
|
|
221
|
+
|
|
222
|
+
const queryData: any = queryConfigs[0].metricQueryData;
|
|
223
|
+
expect(queryData.filterData.metricName).toBe(tc.metricName);
|
|
224
|
+
expect(queryData.filterData.aggegationType).toBe(tc.aggregation);
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* Decision (2): one incident per device — group by the raw datapoint
|
|
228
|
+
* label, which is NOT resource-prefixed.
|
|
229
|
+
*/
|
|
230
|
+
expect(queryData.groupByAttributeKeys).toEqual(["device.id"]);
|
|
231
|
+
expect(queryData.groupByAttributeKeys[0].startsWith("resource.")).toBe(
|
|
232
|
+
false,
|
|
233
|
+
);
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
test.each(IOT_TEMPLATES)(
|
|
238
|
+
"$id unhealthy/healthy criteria partition the range at $threshold",
|
|
239
|
+
(tc: IoTTemplateCase) => {
|
|
240
|
+
const template: IoTAlertTemplate = getIoTAlertTemplateById(tc.id)!;
|
|
241
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
242
|
+
|
|
243
|
+
const instances: Array<any> = step.data?.monitorCriteria.data
|
|
244
|
+
?.monitorCriteriaInstanceArray as Array<any>;
|
|
245
|
+
expect(instances).toHaveLength(2);
|
|
246
|
+
const [offline, online] = instances;
|
|
247
|
+
|
|
248
|
+
const offlineFilter: any = offline.data.filters[0];
|
|
249
|
+
const onlineFilter: any = online.data.filters[0];
|
|
250
|
+
|
|
251
|
+
expect(offlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
252
|
+
tc.metricAlias,
|
|
253
|
+
);
|
|
254
|
+
expect(onlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
255
|
+
tc.metricAlias,
|
|
256
|
+
);
|
|
257
|
+
expect(offlineFilter.value).toBe(tc.threshold);
|
|
258
|
+
expect(onlineFilter.value).toBe(tc.threshold);
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
* Decision (1): pin the exact comparison direction per template — the
|
|
262
|
+
* low-value alerts invert relative to the ceiling alerts.
|
|
263
|
+
*/
|
|
264
|
+
expect(offlineFilter.filterType).toBe(tc.offlineFilterType);
|
|
265
|
+
expect(onlineFilter.filterType).toBe(tc.onlineFilterType);
|
|
266
|
+
|
|
267
|
+
expect(offline.data.createIncidents).toBe(true);
|
|
268
|
+
expect(offline.data.createAlerts).toBe(true);
|
|
269
|
+
expect(online.data.createIncidents).toBe(false);
|
|
270
|
+
expect(online.data.createAlerts).toBe(false);
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
test.each(IOT_TEMPLATES)(
|
|
275
|
+
"$id applies TreatAsZero only when it is the Device Offline template",
|
|
276
|
+
(tc: IoTTemplateCase) => {
|
|
277
|
+
const template: IoTAlertTemplate = getIoTAlertTemplateById(tc.id)!;
|
|
278
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
279
|
+
|
|
280
|
+
const offline: any = (
|
|
281
|
+
step.data?.monitorCriteria.data
|
|
282
|
+
?.monitorCriteriaInstanceArray as Array<any>
|
|
283
|
+
)[0];
|
|
284
|
+
const online: any = (
|
|
285
|
+
step.data?.monitorCriteria.data
|
|
286
|
+
?.monitorCriteriaInstanceArray as Array<any>
|
|
287
|
+
)[1];
|
|
288
|
+
|
|
289
|
+
const offlineNoDataPolicy: NoDataPolicy | undefined =
|
|
290
|
+
offline.data.filters[0].metricMonitorOptions.onNoDataPolicy;
|
|
291
|
+
|
|
292
|
+
if (tc.treatNoDataAsZero) {
|
|
293
|
+
// Decision (3): a silent registered device must fold to 0 and alert.
|
|
294
|
+
expect(offlineNoDataPolicy).toBe(NoDataPolicy.TreatAsZero);
|
|
295
|
+
} else {
|
|
296
|
+
// Everything else leaves no-data untouched to avoid false alarms.
|
|
297
|
+
expect(offlineNoDataPolicy).toBeUndefined();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// The healthy criterion never overrides the no-data policy.
|
|
301
|
+
expect(
|
|
302
|
+
online.data.filters[0].metricMonitorOptions.onNoDataPolicy,
|
|
303
|
+
).toBeUndefined();
|
|
304
|
+
},
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
test("exactly one template opts into TreatAsZero", () => {
|
|
308
|
+
/*
|
|
309
|
+
* Guards decision (3) at the catalog level: if a second template ever
|
|
310
|
+
* enables TreatAsZero, this fails and forces a deliberate review.
|
|
311
|
+
*/
|
|
312
|
+
const withTreatAsZero: Array<IoTTemplateCase> = IOT_TEMPLATES.filter(
|
|
313
|
+
(tc: IoTTemplateCase) => {
|
|
314
|
+
return tc.treatNoDataAsZero;
|
|
315
|
+
},
|
|
316
|
+
);
|
|
317
|
+
expect(withTreatAsZero).toHaveLength(1);
|
|
318
|
+
expect(withTreatAsZero[0]!.id).toBe("iot-device-offline");
|
|
319
|
+
});
|
|
320
|
+
});
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PodmanAlertTemplate,
|
|
3
|
+
PodmanAlertTemplateArgs,
|
|
4
|
+
PodmanAlertTemplateCategory,
|
|
5
|
+
getAllPodmanAlertTemplates,
|
|
6
|
+
getPodmanAlertTemplateById,
|
|
7
|
+
getPodmanAlertTemplatesByCategory,
|
|
8
|
+
} from "../../../Types/Monitor/PodmanAlertTemplates";
|
|
9
|
+
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
10
|
+
import MonitorStepPodmanMonitor from "../../../Types/Monitor/MonitorStepPodmanMonitor";
|
|
11
|
+
import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
|
|
12
|
+
import RollingTime from "../../../Types/RollingTime/RollingTime";
|
|
13
|
+
import { FilterType } from "../../../Types/Monitor/CriteriaFilter";
|
|
14
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Podman containers emit the same OTel container.* metrics as Docker, so the
|
|
18
|
+
* Podman templates mirror the Docker set. These tests lock in the same three
|
|
19
|
+
* regression-prone decisions (metric name, per-minute aggregation, and the
|
|
20
|
+
* unhealthy/healthy range partition) independently, so a divergence between
|
|
21
|
+
* the two catalogs — a renamed metric or a flipped comparison copied from one
|
|
22
|
+
* into the other — is caught here rather than in production.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
interface PodmanTemplateCase {
|
|
26
|
+
id: string;
|
|
27
|
+
category: PodmanAlertTemplateCategory;
|
|
28
|
+
severity: "Critical" | "Warning";
|
|
29
|
+
metricName: string;
|
|
30
|
+
metricAlias: string;
|
|
31
|
+
aggregation: MetricsAggregationType;
|
|
32
|
+
rollingTime: RollingTime;
|
|
33
|
+
offlineFilterType: FilterType;
|
|
34
|
+
onlineFilterType: FilterType;
|
|
35
|
+
threshold: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const PODMAN_TEMPLATES: Array<PodmanTemplateCase> = [
|
|
39
|
+
{
|
|
40
|
+
id: "podman-high-cpu",
|
|
41
|
+
category: "Resource",
|
|
42
|
+
severity: "Warning",
|
|
43
|
+
metricName: "container.cpu.utilization",
|
|
44
|
+
metricAlias: "container_cpu",
|
|
45
|
+
aggregation: MetricsAggregationType.Max,
|
|
46
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
47
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
48
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
49
|
+
threshold: 80,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "podman-high-memory",
|
|
53
|
+
category: "Resource",
|
|
54
|
+
severity: "Warning",
|
|
55
|
+
metricName: "container.memory.percent",
|
|
56
|
+
metricAlias: "container_memory",
|
|
57
|
+
aggregation: MetricsAggregationType.Max,
|
|
58
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
59
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
60
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
61
|
+
threshold: 85,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "podman-restart-loop",
|
|
65
|
+
category: "Container",
|
|
66
|
+
severity: "Critical",
|
|
67
|
+
metricName: "container.restarts",
|
|
68
|
+
metricAlias: "container_restarts",
|
|
69
|
+
aggregation: MetricsAggregationType.Max,
|
|
70
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
71
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
72
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
73
|
+
threshold: 5,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "podman-cpu-throttling",
|
|
77
|
+
category: "Resource",
|
|
78
|
+
severity: "Warning",
|
|
79
|
+
metricName: "container.cpu.throttling_data.throttled_time",
|
|
80
|
+
metricAlias: "cpu_throttled",
|
|
81
|
+
aggregation: MetricsAggregationType.Max,
|
|
82
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
83
|
+
// throttled_time is non-negative: (> 0) unhealthy, (= 0) healthy partitions it.
|
|
84
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
85
|
+
onlineFilterType: FilterType.EqualTo,
|
|
86
|
+
threshold: 0,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "podman-high-pids",
|
|
90
|
+
category: "Container",
|
|
91
|
+
severity: "Warning",
|
|
92
|
+
metricName: "container.pids.count",
|
|
93
|
+
metricAlias: "pids_count",
|
|
94
|
+
aggregation: MetricsAggregationType.Max,
|
|
95
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
96
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
97
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
98
|
+
threshold: 500,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "podman-container-down",
|
|
102
|
+
category: "Container",
|
|
103
|
+
severity: "Critical",
|
|
104
|
+
metricName: "container.uptime",
|
|
105
|
+
metricAlias: "container_uptime",
|
|
106
|
+
aggregation: MetricsAggregationType.Min,
|
|
107
|
+
rollingTime: RollingTime.Past1Minute,
|
|
108
|
+
// uptime is non-negative: (= 0) unhealthy, (> 0) healthy partitions it.
|
|
109
|
+
offlineFilterType: FilterType.EqualTo,
|
|
110
|
+
onlineFilterType: FilterType.GreaterThan,
|
|
111
|
+
threshold: 0,
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
function buildArgs(): PodmanAlertTemplateArgs {
|
|
116
|
+
return {
|
|
117
|
+
hostIdentifier: "podman-host-01",
|
|
118
|
+
onlineMonitorStatusId: ObjectID.generate(),
|
|
119
|
+
offlineMonitorStatusId: ObjectID.generate(),
|
|
120
|
+
defaultIncidentSeverityId: ObjectID.generate(),
|
|
121
|
+
defaultAlertSeverityId: ObjectID.generate(),
|
|
122
|
+
monitorName: "Test Podman Monitor",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getPodmanMonitor(step: MonitorStep): MonitorStepPodmanMonitor {
|
|
127
|
+
const podmanMonitor: MonitorStepPodmanMonitor | undefined =
|
|
128
|
+
step.data?.podmanMonitor;
|
|
129
|
+
if (!podmanMonitor) {
|
|
130
|
+
throw new Error("podmanMonitor missing from monitor step");
|
|
131
|
+
}
|
|
132
|
+
return podmanMonitor;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
describe("PodmanAlertTemplates", () => {
|
|
136
|
+
test("every documented template id is registered and the suite is exhaustive", () => {
|
|
137
|
+
const ids: Array<string> = getAllPodmanAlertTemplates().map(
|
|
138
|
+
(t: PodmanAlertTemplate) => {
|
|
139
|
+
return t.id;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
for (const tc of PODMAN_TEMPLATES) {
|
|
143
|
+
expect(ids).toContain(tc.id);
|
|
144
|
+
}
|
|
145
|
+
expect(ids.sort()).toEqual(
|
|
146
|
+
PODMAN_TEMPLATES.map((t: PodmanTemplateCase) => {
|
|
147
|
+
return t.id;
|
|
148
|
+
}).sort(),
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("every template id is unique", () => {
|
|
153
|
+
const ids: Array<string> = getAllPodmanAlertTemplates().map(
|
|
154
|
+
(t: PodmanAlertTemplate) => {
|
|
155
|
+
return t.id;
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("getPodmanAlertTemplateById returns undefined for an unknown id", () => {
|
|
162
|
+
expect(getPodmanAlertTemplateById("does-not-exist")).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("getPodmanAlertTemplatesByCategory partitions the catalog by category", () => {
|
|
166
|
+
const all: Array<PodmanAlertTemplate> = getAllPodmanAlertTemplates();
|
|
167
|
+
const categories: Array<PodmanAlertTemplateCategory> = [
|
|
168
|
+
"Container",
|
|
169
|
+
"Resource",
|
|
170
|
+
"Host",
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
let total: number = 0;
|
|
174
|
+
for (const category of categories) {
|
|
175
|
+
const inCategory: Array<PodmanAlertTemplate> =
|
|
176
|
+
getPodmanAlertTemplatesByCategory(category);
|
|
177
|
+
for (const template of inCategory) {
|
|
178
|
+
expect(template.category).toBe(category);
|
|
179
|
+
}
|
|
180
|
+
total += inCategory.length;
|
|
181
|
+
}
|
|
182
|
+
expect(total).toBe(all.length);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test.each(PODMAN_TEMPLATES)(
|
|
186
|
+
"$id is a $severity $category template with populated copy",
|
|
187
|
+
(tc: PodmanTemplateCase) => {
|
|
188
|
+
const template: PodmanAlertTemplate | undefined =
|
|
189
|
+
getPodmanAlertTemplateById(tc.id);
|
|
190
|
+
expect(template).toBeDefined();
|
|
191
|
+
expect(template!.category).toBe(tc.category);
|
|
192
|
+
expect(template!.severity).toBe(tc.severity);
|
|
193
|
+
expect(template!.name.length).toBeGreaterThan(0);
|
|
194
|
+
expect(template!.description.length).toBeGreaterThan(0);
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
test.each(PODMAN_TEMPLATES)(
|
|
199
|
+
"$id queries $metricName with the intended aggregation and window",
|
|
200
|
+
(tc: PodmanTemplateCase) => {
|
|
201
|
+
const template: PodmanAlertTemplate = getPodmanAlertTemplateById(tc.id)!;
|
|
202
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
203
|
+
const monitor: MonitorStepPodmanMonitor = getPodmanMonitor(step);
|
|
204
|
+
|
|
205
|
+
expect(monitor.hostIdentifier).toBe("podman-host-01");
|
|
206
|
+
|
|
207
|
+
const queryConfigs: Array<any> = monitor.metricViewConfig
|
|
208
|
+
.queryConfigs as Array<any>;
|
|
209
|
+
expect(queryConfigs).toHaveLength(1);
|
|
210
|
+
expect(monitor.metricViewConfig.formulaConfigs).toHaveLength(0);
|
|
211
|
+
|
|
212
|
+
const filterData: any = queryConfigs[0].metricQueryData.filterData;
|
|
213
|
+
expect(filterData.metricName).toBe(tc.metricName);
|
|
214
|
+
expect(filterData.aggegationType).toBe(tc.aggregation);
|
|
215
|
+
expect(monitor.rollingTime).toBe(tc.rollingTime);
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
test.each(PODMAN_TEMPLATES)(
|
|
220
|
+
"$id unhealthy/healthy criteria partition the range at $threshold",
|
|
221
|
+
(tc: PodmanTemplateCase) => {
|
|
222
|
+
const template: PodmanAlertTemplate = getPodmanAlertTemplateById(tc.id)!;
|
|
223
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
224
|
+
|
|
225
|
+
const instances: Array<any> = step.data?.monitorCriteria.data
|
|
226
|
+
?.monitorCriteriaInstanceArray as Array<any>;
|
|
227
|
+
expect(instances).toHaveLength(2);
|
|
228
|
+
const [offline, online] = instances;
|
|
229
|
+
|
|
230
|
+
const offlineFilter: any = offline.data.filters[0];
|
|
231
|
+
const onlineFilter: any = online.data.filters[0];
|
|
232
|
+
|
|
233
|
+
expect(offlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
234
|
+
tc.metricAlias,
|
|
235
|
+
);
|
|
236
|
+
expect(onlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
237
|
+
tc.metricAlias,
|
|
238
|
+
);
|
|
239
|
+
expect(offlineFilter.value).toBe(tc.threshold);
|
|
240
|
+
expect(onlineFilter.value).toBe(tc.threshold);
|
|
241
|
+
|
|
242
|
+
expect(offlineFilter.filterType).toBe(tc.offlineFilterType);
|
|
243
|
+
expect(onlineFilter.filterType).toBe(tc.onlineFilterType);
|
|
244
|
+
|
|
245
|
+
expect(offline.data.createIncidents).toBe(true);
|
|
246
|
+
expect(offline.data.createAlerts).toBe(true);
|
|
247
|
+
expect(offline.data.incidents).toHaveLength(1);
|
|
248
|
+
expect(offline.data.alerts).toHaveLength(1);
|
|
249
|
+
expect(offline.data.monitorStatusId).toBeDefined();
|
|
250
|
+
|
|
251
|
+
expect(online.data.createIncidents).toBe(false);
|
|
252
|
+
expect(online.data.createAlerts).toBe(false);
|
|
253
|
+
expect(online.data.incidents).toHaveLength(0);
|
|
254
|
+
expect(online.data.alerts).toHaveLength(0);
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
test("incidents auto-resolve so a recovered container clears itself", () => {
|
|
259
|
+
for (const tc of PODMAN_TEMPLATES) {
|
|
260
|
+
const step: MonitorStep = getPodmanAlertTemplateById(
|
|
261
|
+
tc.id,
|
|
262
|
+
)!.getMonitorStep(buildArgs());
|
|
263
|
+
const offline: any = (
|
|
264
|
+
step.data?.monitorCriteria.data
|
|
265
|
+
?.monitorCriteriaInstanceArray as Array<any>
|
|
266
|
+
)[0];
|
|
267
|
+
expect(offline.data.incidents[0].autoResolveIncident).toBe(true);
|
|
268
|
+
expect(offline.data.alerts[0].autoResolveAlert).toBe(true);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|