@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,293 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DockerAlertTemplate,
|
|
3
|
+
DockerAlertTemplateArgs,
|
|
4
|
+
DockerAlertTemplateCategory,
|
|
5
|
+
getAllDockerAlertTemplates,
|
|
6
|
+
getDockerAlertTemplateById,
|
|
7
|
+
getDockerAlertTemplatesByCategory,
|
|
8
|
+
} from "../../../Types/Monitor/DockerAlertTemplates";
|
|
9
|
+
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
10
|
+
import MonitorStepDockerMonitor from "../../../Types/Monitor/MonitorStepDockerMonitor";
|
|
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
|
+
* These tests lock in the container-alert templates' easy-to-regress
|
|
18
|
+
* decisions:
|
|
19
|
+
*
|
|
20
|
+
* 1. The metric name each template queries — a rename in the collector
|
|
21
|
+
* pipeline (e.g. `container.memory.percent`) would silently make the
|
|
22
|
+
* template match nothing while every "template exists" check stays green.
|
|
23
|
+
*
|
|
24
|
+
* 2. The per-minute aggregation. Container templates deliberately use `Max`
|
|
25
|
+
* so a SINGLE hot/throttled/forking container trips the threshold instead
|
|
26
|
+
* of being diluted by averaging across the other containers on the host;
|
|
27
|
+
* the down/uptime template uses `Min` so a single zero-uptime scrape wins.
|
|
28
|
+
*
|
|
29
|
+
* 3. The unhealthy and healthy criteria PARTITION the value range — no gap
|
|
30
|
+
* (a value matching neither leaves the monitor stuck in its previous
|
|
31
|
+
* status) and no overlap (a value matching both makes the status depend
|
|
32
|
+
* on evaluation order). The failure mode is a strict/non-strict slip.
|
|
33
|
+
* The two zero-boundary templates (throttling, container-down) partition
|
|
34
|
+
* the non-negative domain with `> 0` / `= 0`, so they are pinned per case
|
|
35
|
+
* rather than by a generic complement rule.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
interface DockerTemplateCase {
|
|
39
|
+
id: string;
|
|
40
|
+
category: DockerAlertTemplateCategory;
|
|
41
|
+
severity: "Critical" | "Warning";
|
|
42
|
+
metricName: string;
|
|
43
|
+
metricAlias: string;
|
|
44
|
+
aggregation: MetricsAggregationType;
|
|
45
|
+
rollingTime: RollingTime;
|
|
46
|
+
offlineFilterType: FilterType;
|
|
47
|
+
onlineFilterType: FilterType;
|
|
48
|
+
threshold: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DOCKER_TEMPLATES: Array<DockerTemplateCase> = [
|
|
52
|
+
{
|
|
53
|
+
id: "docker-high-cpu",
|
|
54
|
+
category: "Resource",
|
|
55
|
+
severity: "Warning",
|
|
56
|
+
metricName: "container.cpu.utilization",
|
|
57
|
+
metricAlias: "container_cpu",
|
|
58
|
+
aggregation: MetricsAggregationType.Max,
|
|
59
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
60
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
61
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
62
|
+
threshold: 80,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "docker-high-memory",
|
|
66
|
+
category: "Resource",
|
|
67
|
+
severity: "Warning",
|
|
68
|
+
metricName: "container.memory.percent",
|
|
69
|
+
metricAlias: "container_memory",
|
|
70
|
+
aggregation: MetricsAggregationType.Max,
|
|
71
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
72
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
73
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
74
|
+
threshold: 85,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "docker-restart-loop",
|
|
78
|
+
category: "Container",
|
|
79
|
+
severity: "Critical",
|
|
80
|
+
metricName: "container.restarts",
|
|
81
|
+
metricAlias: "container_restarts",
|
|
82
|
+
aggregation: MetricsAggregationType.Max,
|
|
83
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
84
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
85
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
86
|
+
threshold: 5,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "docker-cpu-throttling",
|
|
90
|
+
category: "Resource",
|
|
91
|
+
severity: "Warning",
|
|
92
|
+
metricName: "container.cpu.throttling_data.throttled_time",
|
|
93
|
+
metricAlias: "cpu_throttled",
|
|
94
|
+
aggregation: MetricsAggregationType.Max,
|
|
95
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
96
|
+
// throttled_time is non-negative: (> 0) unhealthy, (= 0) healthy partitions it.
|
|
97
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
98
|
+
onlineFilterType: FilterType.EqualTo,
|
|
99
|
+
threshold: 0,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "docker-high-pids",
|
|
103
|
+
category: "Container",
|
|
104
|
+
severity: "Warning",
|
|
105
|
+
metricName: "container.pids.count",
|
|
106
|
+
metricAlias: "pids_count",
|
|
107
|
+
aggregation: MetricsAggregationType.Max,
|
|
108
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
109
|
+
offlineFilterType: FilterType.GreaterThan,
|
|
110
|
+
onlineFilterType: FilterType.LessThanOrEqualTo,
|
|
111
|
+
threshold: 500,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "docker-container-down",
|
|
115
|
+
category: "Container",
|
|
116
|
+
severity: "Critical",
|
|
117
|
+
metricName: "container.uptime",
|
|
118
|
+
metricAlias: "container_uptime",
|
|
119
|
+
// A single zero-uptime scrape means the container is down right now.
|
|
120
|
+
aggregation: MetricsAggregationType.Min,
|
|
121
|
+
rollingTime: RollingTime.Past1Minute,
|
|
122
|
+
// uptime is non-negative: (= 0) unhealthy, (> 0) healthy partitions it.
|
|
123
|
+
offlineFilterType: FilterType.EqualTo,
|
|
124
|
+
onlineFilterType: FilterType.GreaterThan,
|
|
125
|
+
threshold: 0,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
function buildArgs(): DockerAlertTemplateArgs {
|
|
130
|
+
return {
|
|
131
|
+
hostIdentifier: "docker-host-01",
|
|
132
|
+
onlineMonitorStatusId: ObjectID.generate(),
|
|
133
|
+
offlineMonitorStatusId: ObjectID.generate(),
|
|
134
|
+
defaultIncidentSeverityId: ObjectID.generate(),
|
|
135
|
+
defaultAlertSeverityId: ObjectID.generate(),
|
|
136
|
+
monitorName: "Test Docker Monitor",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getDockerMonitor(step: MonitorStep): MonitorStepDockerMonitor {
|
|
141
|
+
const dockerMonitor: MonitorStepDockerMonitor | undefined =
|
|
142
|
+
step.data?.dockerMonitor;
|
|
143
|
+
if (!dockerMonitor) {
|
|
144
|
+
throw new Error("dockerMonitor missing from monitor step");
|
|
145
|
+
}
|
|
146
|
+
return dockerMonitor;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
describe("DockerAlertTemplates", () => {
|
|
150
|
+
test("every documented template id is registered", () => {
|
|
151
|
+
const ids: Array<string> = getAllDockerAlertTemplates().map(
|
|
152
|
+
(t: DockerAlertTemplate) => {
|
|
153
|
+
return t.id;
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
for (const tc of DOCKER_TEMPLATES) {
|
|
157
|
+
expect(ids).toContain(tc.id);
|
|
158
|
+
}
|
|
159
|
+
// The suite must be exhaustive: no template ships without a case here.
|
|
160
|
+
expect(ids.sort()).toEqual(
|
|
161
|
+
DOCKER_TEMPLATES.map((t: DockerTemplateCase) => {
|
|
162
|
+
return t.id;
|
|
163
|
+
}).sort(),
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("every template id is unique", () => {
|
|
168
|
+
const ids: Array<string> = getAllDockerAlertTemplates().map(
|
|
169
|
+
(t: DockerAlertTemplate) => {
|
|
170
|
+
return t.id;
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("getDockerAlertTemplateById returns undefined for an unknown id", () => {
|
|
177
|
+
expect(getDockerAlertTemplateById("does-not-exist")).toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("getDockerAlertTemplatesByCategory partitions the catalog by category", () => {
|
|
181
|
+
const all: Array<DockerAlertTemplate> = getAllDockerAlertTemplates();
|
|
182
|
+
const categories: Array<DockerAlertTemplateCategory> = [
|
|
183
|
+
"Container",
|
|
184
|
+
"Resource",
|
|
185
|
+
"Host",
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
let total: number = 0;
|
|
189
|
+
for (const category of categories) {
|
|
190
|
+
const inCategory: Array<DockerAlertTemplate> =
|
|
191
|
+
getDockerAlertTemplatesByCategory(category);
|
|
192
|
+
for (const template of inCategory) {
|
|
193
|
+
expect(template.category).toBe(category);
|
|
194
|
+
}
|
|
195
|
+
total += inCategory.length;
|
|
196
|
+
}
|
|
197
|
+
// Every template falls into exactly one of the known categories.
|
|
198
|
+
expect(total).toBe(all.length);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test.each(DOCKER_TEMPLATES)(
|
|
202
|
+
"$id is a $severity $category template with populated copy",
|
|
203
|
+
(tc: DockerTemplateCase) => {
|
|
204
|
+
const template: DockerAlertTemplate | undefined =
|
|
205
|
+
getDockerAlertTemplateById(tc.id);
|
|
206
|
+
expect(template).toBeDefined();
|
|
207
|
+
expect(template!.category).toBe(tc.category);
|
|
208
|
+
expect(template!.severity).toBe(tc.severity);
|
|
209
|
+
expect(template!.name.length).toBeGreaterThan(0);
|
|
210
|
+
expect(template!.description.length).toBeGreaterThan(0);
|
|
211
|
+
},
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
test.each(DOCKER_TEMPLATES)(
|
|
215
|
+
"$id queries $metricName with the intended aggregation and window",
|
|
216
|
+
(tc: DockerTemplateCase) => {
|
|
217
|
+
const template: DockerAlertTemplate = getDockerAlertTemplateById(tc.id)!;
|
|
218
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
219
|
+
const monitor: MonitorStepDockerMonitor = getDockerMonitor(step);
|
|
220
|
+
|
|
221
|
+
expect(monitor.hostIdentifier).toBe("docker-host-01");
|
|
222
|
+
|
|
223
|
+
const queryConfigs: Array<any> = monitor.metricViewConfig
|
|
224
|
+
.queryConfigs as Array<any>;
|
|
225
|
+
// A single-metric threshold template has exactly one query, no formula.
|
|
226
|
+
expect(queryConfigs).toHaveLength(1);
|
|
227
|
+
expect(monitor.metricViewConfig.formulaConfigs).toHaveLength(0);
|
|
228
|
+
|
|
229
|
+
const filterData: any = queryConfigs[0].metricQueryData.filterData;
|
|
230
|
+
expect(filterData.metricName).toBe(tc.metricName);
|
|
231
|
+
expect(filterData.aggegationType).toBe(tc.aggregation);
|
|
232
|
+
expect(monitor.rollingTime).toBe(tc.rollingTime);
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
test.each(DOCKER_TEMPLATES)(
|
|
237
|
+
"$id unhealthy/healthy criteria partition the range at $threshold",
|
|
238
|
+
(tc: DockerTemplateCase) => {
|
|
239
|
+
const template: DockerAlertTemplate = getDockerAlertTemplateById(tc.id)!;
|
|
240
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
241
|
+
|
|
242
|
+
const instances: Array<any> = step.data?.monitorCriteria.data
|
|
243
|
+
?.monitorCriteriaInstanceArray as Array<any>;
|
|
244
|
+
// Exactly two criteria: [unhealthy, healthy].
|
|
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
|
+
// Both criteria evaluate the SAME metric alias at the SAME threshold.
|
|
252
|
+
expect(offlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
253
|
+
tc.metricAlias,
|
|
254
|
+
);
|
|
255
|
+
expect(onlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
256
|
+
tc.metricAlias,
|
|
257
|
+
);
|
|
258
|
+
expect(offlineFilter.value).toBe(tc.threshold);
|
|
259
|
+
expect(onlineFilter.value).toBe(tc.threshold);
|
|
260
|
+
|
|
261
|
+
// Only the comparison DIRECTION differs — pinned per template.
|
|
262
|
+
expect(offlineFilter.filterType).toBe(tc.offlineFilterType);
|
|
263
|
+
expect(onlineFilter.filterType).toBe(tc.onlineFilterType);
|
|
264
|
+
|
|
265
|
+
// Only the unhealthy criterion opens incidents/alerts.
|
|
266
|
+
expect(offline.data.createIncidents).toBe(true);
|
|
267
|
+
expect(offline.data.createAlerts).toBe(true);
|
|
268
|
+
expect(offline.data.incidents).toHaveLength(1);
|
|
269
|
+
expect(offline.data.alerts).toHaveLength(1);
|
|
270
|
+
expect(offline.data.monitorStatusId).toBeDefined();
|
|
271
|
+
|
|
272
|
+
expect(online.data.createIncidents).toBe(false);
|
|
273
|
+
expect(online.data.createAlerts).toBe(false);
|
|
274
|
+
expect(online.data.incidents).toHaveLength(0);
|
|
275
|
+
expect(online.data.alerts).toHaveLength(0);
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
test("incidents auto-resolve so a recovered container clears itself", () => {
|
|
280
|
+
// A container alert must not linger once the container is healthy again.
|
|
281
|
+
for (const tc of DOCKER_TEMPLATES) {
|
|
282
|
+
const step: MonitorStep = getDockerAlertTemplateById(
|
|
283
|
+
tc.id,
|
|
284
|
+
)!.getMonitorStep(buildArgs());
|
|
285
|
+
const offline: any = (
|
|
286
|
+
step.data?.monitorCriteria.data
|
|
287
|
+
?.monitorCriteriaInstanceArray as Array<any>
|
|
288
|
+
)[0];
|
|
289
|
+
expect(offline.data.incidents[0].autoResolveIncident).toBe(true);
|
|
290
|
+
expect(offline.data.alerts[0].autoResolveAlert).toBe(true);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HostAlertTemplate,
|
|
3
|
+
HostAlertTemplateArgs,
|
|
4
|
+
HostAlertTemplateCategory,
|
|
5
|
+
getAllHostAlertTemplates,
|
|
6
|
+
getHostAlertTemplateById,
|
|
7
|
+
getHostAlertTemplatesByCategory,
|
|
8
|
+
} from "../../../Types/Monitor/HostAlertTemplates";
|
|
9
|
+
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
10
|
+
import MonitorStepHostMonitor from "../../../Types/Monitor/MonitorStepHostMonitor";
|
|
11
|
+
import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
|
|
12
|
+
import { FilterType } from "../../../Types/Monitor/CriteriaFilter";
|
|
13
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Host resource templates. The regression-prone details these tests pin:
|
|
17
|
+
*
|
|
18
|
+
* 1. The OTel metric name each template reads (system.cpu.utilization,
|
|
19
|
+
* system.memory.utilization, ...). A rename in the host receiver would
|
|
20
|
+
* make the template match nothing while staying "registered".
|
|
21
|
+
*
|
|
22
|
+
* 2. Ratio metrics are on a [0, 1] scale, so a "90%" threshold is the
|
|
23
|
+
* literal 0.9 — NOT 90. A slip to 90 makes the utilization templates
|
|
24
|
+
* unreachable (utilization never exceeds 1). Load average and process
|
|
25
|
+
* count are raw counts, so their thresholds (4, 2000) are literal.
|
|
26
|
+
*
|
|
27
|
+
* 3. Aggregation: filesystem usage and process count use `Max` so a single
|
|
28
|
+
* full mount / one runaway host trips the alert instead of being averaged
|
|
29
|
+
* away; the rest use `Avg` for a representative sustained reading.
|
|
30
|
+
*
|
|
31
|
+
* 4. The unhealthy/healthy criteria partition the range at the threshold
|
|
32
|
+
* with `>` / `<=` — no gap, no overlap.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
interface HostTemplateCase {
|
|
36
|
+
id: string;
|
|
37
|
+
category: HostAlertTemplateCategory;
|
|
38
|
+
severity: "Critical" | "Warning";
|
|
39
|
+
metricName: string;
|
|
40
|
+
metricAlias: string;
|
|
41
|
+
aggregation: MetricsAggregationType;
|
|
42
|
+
threshold: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const HOST_TEMPLATES: Array<HostTemplateCase> = [
|
|
46
|
+
{
|
|
47
|
+
id: "host-high-cpu",
|
|
48
|
+
category: "Resource",
|
|
49
|
+
severity: "Warning",
|
|
50
|
+
metricName: "system.cpu.utilization",
|
|
51
|
+
metricAlias: "host_cpu",
|
|
52
|
+
aggregation: MetricsAggregationType.Avg,
|
|
53
|
+
threshold: 0.8,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "host-high-memory",
|
|
57
|
+
category: "Resource",
|
|
58
|
+
severity: "Warning",
|
|
59
|
+
metricName: "system.memory.utilization",
|
|
60
|
+
metricAlias: "host_memory",
|
|
61
|
+
aggregation: MetricsAggregationType.Avg,
|
|
62
|
+
threshold: 0.85,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "host-high-filesystem",
|
|
66
|
+
category: "Resource",
|
|
67
|
+
severity: "Critical",
|
|
68
|
+
metricName: "system.filesystem.utilization",
|
|
69
|
+
metricAlias: "host_filesystem",
|
|
70
|
+
// Max so a single full filesystem is not diluted by averaging mounts.
|
|
71
|
+
aggregation: MetricsAggregationType.Max,
|
|
72
|
+
threshold: 0.9,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "host-high-load-average",
|
|
76
|
+
category: "Resource",
|
|
77
|
+
severity: "Warning",
|
|
78
|
+
metricName: "system.cpu.load_average.1m",
|
|
79
|
+
metricAlias: "host_load_1m",
|
|
80
|
+
aggregation: MetricsAggregationType.Avg,
|
|
81
|
+
// Load average is a raw count, not a ratio — threshold is literal 4.
|
|
82
|
+
threshold: 4,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "host-high-processes",
|
|
86
|
+
category: "Host",
|
|
87
|
+
severity: "Warning",
|
|
88
|
+
metricName: "system.processes.count",
|
|
89
|
+
metricAlias: "host_processes",
|
|
90
|
+
aggregation: MetricsAggregationType.Max,
|
|
91
|
+
threshold: 2000,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
function buildArgs(): HostAlertTemplateArgs {
|
|
96
|
+
return {
|
|
97
|
+
hostIdentifier: "host-01",
|
|
98
|
+
onlineMonitorStatusId: ObjectID.generate(),
|
|
99
|
+
offlineMonitorStatusId: ObjectID.generate(),
|
|
100
|
+
defaultIncidentSeverityId: ObjectID.generate(),
|
|
101
|
+
defaultAlertSeverityId: ObjectID.generate(),
|
|
102
|
+
monitorName: "Test Host Monitor",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getHostMonitor(step: MonitorStep): MonitorStepHostMonitor {
|
|
107
|
+
const hostMonitor: MonitorStepHostMonitor | undefined =
|
|
108
|
+
step.data?.hostMonitor;
|
|
109
|
+
if (!hostMonitor) {
|
|
110
|
+
throw new Error("hostMonitor missing from monitor step");
|
|
111
|
+
}
|
|
112
|
+
return hostMonitor;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
describe("HostAlertTemplates", () => {
|
|
116
|
+
test("every documented template id is registered and the suite is exhaustive", () => {
|
|
117
|
+
const ids: Array<string> = getAllHostAlertTemplates().map(
|
|
118
|
+
(t: HostAlertTemplate) => {
|
|
119
|
+
return t.id;
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
for (const tc of HOST_TEMPLATES) {
|
|
123
|
+
expect(ids).toContain(tc.id);
|
|
124
|
+
}
|
|
125
|
+
expect(ids.sort()).toEqual(
|
|
126
|
+
HOST_TEMPLATES.map((t: HostTemplateCase) => {
|
|
127
|
+
return t.id;
|
|
128
|
+
}).sort(),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("every template id is unique", () => {
|
|
133
|
+
const ids: Array<string> = getAllHostAlertTemplates().map(
|
|
134
|
+
(t: HostAlertTemplate) => {
|
|
135
|
+
return t.id;
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("getHostAlertTemplateById returns undefined for an unknown id", () => {
|
|
142
|
+
expect(getHostAlertTemplateById("host-nothing")).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("getHostAlertTemplatesByCategory returns only that category and covers the catalog", () => {
|
|
146
|
+
const all: Array<HostAlertTemplate> = getAllHostAlertTemplates();
|
|
147
|
+
const categories: Array<HostAlertTemplateCategory> = ["Resource", "Host"];
|
|
148
|
+
|
|
149
|
+
let total: number = 0;
|
|
150
|
+
for (const category of categories) {
|
|
151
|
+
const inCategory: Array<HostAlertTemplate> =
|
|
152
|
+
getHostAlertTemplatesByCategory(category);
|
|
153
|
+
for (const template of inCategory) {
|
|
154
|
+
expect(template.category).toBe(category);
|
|
155
|
+
}
|
|
156
|
+
total += inCategory.length;
|
|
157
|
+
}
|
|
158
|
+
expect(total).toBe(all.length);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test.each(HOST_TEMPLATES)(
|
|
162
|
+
"$id is a $severity $category template with populated copy",
|
|
163
|
+
(tc: HostTemplateCase) => {
|
|
164
|
+
const template: HostAlertTemplate | undefined = getHostAlertTemplateById(
|
|
165
|
+
tc.id,
|
|
166
|
+
);
|
|
167
|
+
expect(template).toBeDefined();
|
|
168
|
+
expect(template!.category).toBe(tc.category);
|
|
169
|
+
expect(template!.severity).toBe(tc.severity);
|
|
170
|
+
expect(template!.name.length).toBeGreaterThan(0);
|
|
171
|
+
expect(template!.description.length).toBeGreaterThan(0);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
test.each(HOST_TEMPLATES)(
|
|
176
|
+
"$id queries $metricName with the intended aggregation",
|
|
177
|
+
(tc: HostTemplateCase) => {
|
|
178
|
+
const template: HostAlertTemplate = getHostAlertTemplateById(tc.id)!;
|
|
179
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
180
|
+
const monitor: MonitorStepHostMonitor = getHostMonitor(step);
|
|
181
|
+
|
|
182
|
+
expect(monitor.hostIdentifier).toBe("host-01");
|
|
183
|
+
|
|
184
|
+
const queryConfigs: Array<any> = monitor.metricViewConfig
|
|
185
|
+
.queryConfigs as Array<any>;
|
|
186
|
+
expect(queryConfigs).toHaveLength(1);
|
|
187
|
+
expect(monitor.metricViewConfig.formulaConfigs).toHaveLength(0);
|
|
188
|
+
|
|
189
|
+
const filterData: any = queryConfigs[0].metricQueryData.filterData;
|
|
190
|
+
expect(filterData.metricName).toBe(tc.metricName);
|
|
191
|
+
expect(filterData.aggegationType).toBe(tc.aggregation);
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
test.each(HOST_TEMPLATES)(
|
|
196
|
+
"$id unhealthy/healthy criteria partition the range at $threshold with > / <=",
|
|
197
|
+
(tc: HostTemplateCase) => {
|
|
198
|
+
const template: HostAlertTemplate = getHostAlertTemplateById(tc.id)!;
|
|
199
|
+
const step: MonitorStep = template.getMonitorStep(buildArgs());
|
|
200
|
+
|
|
201
|
+
const instances: Array<any> = step.data?.monitorCriteria.data
|
|
202
|
+
?.monitorCriteriaInstanceArray as Array<any>;
|
|
203
|
+
expect(instances).toHaveLength(2);
|
|
204
|
+
const [offline, online] = instances;
|
|
205
|
+
|
|
206
|
+
const offlineFilter: any = offline.data.filters[0];
|
|
207
|
+
const onlineFilter: any = online.data.filters[0];
|
|
208
|
+
|
|
209
|
+
expect(offlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
210
|
+
tc.metricAlias,
|
|
211
|
+
);
|
|
212
|
+
expect(onlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
213
|
+
tc.metricAlias,
|
|
214
|
+
);
|
|
215
|
+
expect(offlineFilter.value).toBe(tc.threshold);
|
|
216
|
+
expect(onlineFilter.value).toBe(tc.threshold);
|
|
217
|
+
|
|
218
|
+
// Host ceilings are all "> threshold" unhealthy / "<= threshold" healthy.
|
|
219
|
+
expect(offlineFilter.filterType).toBe(FilterType.GreaterThan);
|
|
220
|
+
expect(onlineFilter.filterType).toBe(FilterType.LessThanOrEqualTo);
|
|
221
|
+
|
|
222
|
+
expect(offline.data.createIncidents).toBe(true);
|
|
223
|
+
expect(offline.data.createAlerts).toBe(true);
|
|
224
|
+
expect(online.data.createIncidents).toBe(false);
|
|
225
|
+
expect(online.data.createAlerts).toBe(false);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
test("ratio-metric thresholds stay on the 0-1 scale, not 0-100", () => {
|
|
230
|
+
/*
|
|
231
|
+
* A "90%" threshold written as 90 would make a utilization ([0,1])
|
|
232
|
+
* template unreachable. Lock the utilization templates below 1.
|
|
233
|
+
*/
|
|
234
|
+
const ratioIds: Array<string> = [
|
|
235
|
+
"host-high-cpu",
|
|
236
|
+
"host-high-memory",
|
|
237
|
+
"host-high-filesystem",
|
|
238
|
+
];
|
|
239
|
+
for (const id of ratioIds) {
|
|
240
|
+
const tc: HostTemplateCase = HOST_TEMPLATES.find(
|
|
241
|
+
(t: HostTemplateCase) => {
|
|
242
|
+
return t.id === id;
|
|
243
|
+
},
|
|
244
|
+
)!;
|
|
245
|
+
expect(tc.threshold).toBeGreaterThan(0);
|
|
246
|
+
expect(tc.threshold).toBeLessThanOrEqual(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|