@oneuptime/common 10.0.71 → 10.0.73
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/Alert.ts +55 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/Project.ts +26 -0
- package/Models/DatabaseModels/Service.ts +2 -2
- package/Models/DatabaseModels/StatusPage.ts +80 -0
- package/Server/API/StatusPageAPI.ts +4 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1777018175127-AddTelemetryRetentionSettings.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AnalyticsDatabaseService.ts +17 -7
- package/Server/Services/OpenTelemetryIngestService.ts +36 -5
- package/Server/Services/ServiceService.ts +27 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +175 -29
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +71 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +91 -7
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +171 -2
- package/Server/Utils/Monitor/MonitorIncident.ts +133 -8
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +423 -1
- package/Server/Utils/Monitor/MonitorResource.ts +2 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +99 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +268 -0
- package/Types/AdminDashboard/AdminDashboardLanguage.ts +33 -0
- package/Types/Infrastructure/BasicMetrics.ts +75 -0
- package/Types/Metrics/MetricQueryData.ts +11 -0
- package/Types/Monitor/CriteriaFilter.ts +10 -0
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +11 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +10 -0
- package/Types/Monitor/MetricMonitor/MetricSeriesResult.ts +20 -0
- package/Types/Monitor/MonitorMetricType.ts +34 -0
- package/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts +8 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +25 -0
- package/Types/StatusPage/StatusPageLanguage.ts +30 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +17 -12
- package/UI/Components/Charts/Bar/BarChart.tsx +16 -11
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -0
- package/UI/Components/Charts/Line/LineChart.tsx +16 -11
- package/UI/Components/Filters/FiltersForm.tsx +26 -2
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +453 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +229 -0
- package/Utils/Metrics/MetricSeriesFingerprint.ts +97 -0
- package/Utils/Monitor/MonitorMetricType.ts +309 -19
- package/build/dist/Models/DatabaseModels/Alert.js +57 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +57 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +27 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +1 -2
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +4 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777018175127-AddTelemetryRetentionSettings.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777018175127-AddTelemetryRetentionSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +14 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +30 -4
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +22 -1
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +132 -30
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +58 -7
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +66 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +112 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +91 -15
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +373 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +2 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +65 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js +199 -0
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js.map +1 -1
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js +22 -0
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js.map +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js +10 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js +2 -0
- package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +28 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageLanguage.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -12
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +12 -11
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +11 -3
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +12 -11
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Filters/FiltersForm.js +6 -2
- package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +383 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +109 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js +81 -0
- package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js +287 -19
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,9 @@ import SSLMonitorCriteria from "./Criteria/SSLMonitorCriteria";
|
|
|
9
9
|
import ServerMonitorCriteria from "./Criteria/ServerMonitorCriteria";
|
|
10
10
|
import SyntheticMonitoringCriteria from "./Criteria/SyntheticMonitor";
|
|
11
11
|
import LogMonitorCriteria from "./Criteria/LogMonitorCriteria";
|
|
12
|
-
import MetricMonitorCriteria
|
|
12
|
+
import MetricMonitorCriteria, {
|
|
13
|
+
MetricSeriesEvaluationResult,
|
|
14
|
+
} from "./Criteria/MetricMonitorCriteria";
|
|
13
15
|
import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
|
|
14
16
|
import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
|
|
15
17
|
import ProfileMonitorCriteria from "./Criteria/ProfileMonitorCriteria";
|
|
@@ -31,7 +33,9 @@ import MonitorEvaluationSummary, {
|
|
|
31
33
|
MonitorEvaluationEvent,
|
|
32
34
|
MonitorEvaluationFilterResult,
|
|
33
35
|
} from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
34
|
-
import ProbeApiIngestResponse
|
|
36
|
+
import ProbeApiIngestResponse, {
|
|
37
|
+
PerSeriesCriteriaMatch,
|
|
38
|
+
} from "../../../Types/Probe/ProbeApiIngestResponse";
|
|
35
39
|
import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
|
|
36
40
|
import RequestFailedDetails from "../../../Types/Probe/RequestFailedDetails";
|
|
37
41
|
import IncomingMonitorRequest from "../../../Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
|
|
@@ -50,6 +54,7 @@ import MetricMonitorResponse, {
|
|
|
50
54
|
KubernetesAffectedResource,
|
|
51
55
|
KubernetesResourceBreakdown,
|
|
52
56
|
} from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
57
|
+
import MetricSeriesResult from "../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
|
|
53
58
|
import MetricCriteriaContext, {
|
|
54
59
|
MetricBreachingSample,
|
|
55
60
|
MetricComponent,
|
|
@@ -156,6 +161,26 @@ ${contextBlock}
|
|
|
156
161
|
**Cause**: ${(input.dataToProcess as ProbeMonitorResponse).failureCause || ""}
|
|
157
162
|
`;
|
|
158
163
|
}
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* When this is a metric-style monitor with per-series results,
|
|
167
|
+
* compute the per-series match list so MonitorResource can fan
|
|
168
|
+
* out one incident per affected series. We do this *after* the
|
|
169
|
+
* scalar criteriaMetId/rootCause is populated so legacy readers
|
|
170
|
+
* still see a usable response if perSeriesMatches is ignored.
|
|
171
|
+
*/
|
|
172
|
+
const perSeriesMatches: Array<PerSeriesCriteriaMatch> =
|
|
173
|
+
MonitorCriteriaEvaluator.collectPerSeriesMatches({
|
|
174
|
+
dataToProcess: input.dataToProcess,
|
|
175
|
+
monitor: input.monitor,
|
|
176
|
+
monitorStep: input.monitorStep,
|
|
177
|
+
criteriaInstance,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (perSeriesMatches.length > 0) {
|
|
181
|
+
input.probeApiIngestResponse.perSeriesMatches = perSeriesMatches;
|
|
182
|
+
}
|
|
183
|
+
|
|
159
184
|
break;
|
|
160
185
|
}
|
|
161
186
|
}
|
|
@@ -163,6 +188,150 @@ ${contextBlock}
|
|
|
163
188
|
return input.probeApiIngestResponse;
|
|
164
189
|
}
|
|
165
190
|
|
|
191
|
+
/**
|
|
192
|
+
* For metric-backed monitors (Metrics/Kubernetes/Docker) with
|
|
193
|
+
* per-series aggregated results, re-evaluate the matched criteria
|
|
194
|
+
* once per series and return one entry per series that breached.
|
|
195
|
+
* Returns an empty array when the monitor is not series-aware or
|
|
196
|
+
* no series matched — the caller falls back to the single-incident
|
|
197
|
+
* path in that case.
|
|
198
|
+
*/
|
|
199
|
+
private static collectPerSeriesMatches(input: {
|
|
200
|
+
dataToProcess: DataToProcess;
|
|
201
|
+
monitor: Monitor;
|
|
202
|
+
monitorStep: MonitorStep;
|
|
203
|
+
criteriaInstance: MonitorCriteriaInstance;
|
|
204
|
+
}): Array<PerSeriesCriteriaMatch> {
|
|
205
|
+
if (
|
|
206
|
+
input.monitor.monitorType !== MonitorType.Metrics &&
|
|
207
|
+
input.monitor.monitorType !== MonitorType.Kubernetes &&
|
|
208
|
+
input.monitor.monitorType !== MonitorType.Docker
|
|
209
|
+
) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const metricResponse: MetricMonitorResponse =
|
|
214
|
+
input.dataToProcess as MetricMonitorResponse;
|
|
215
|
+
|
|
216
|
+
const seriesBreakdown: Array<MetricSeriesResult> | undefined =
|
|
217
|
+
metricResponse.seriesBreakdown;
|
|
218
|
+
|
|
219
|
+
if (!seriesBreakdown || seriesBreakdown.length === 0) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const criteriaId: string | undefined = input.criteriaInstance.data?.id;
|
|
224
|
+
if (!criteriaId) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const filterCondition: FilterCondition =
|
|
229
|
+
input.criteriaInstance.data?.filterCondition || FilterCondition.All;
|
|
230
|
+
|
|
231
|
+
const filters: Array<CriteriaFilter> =
|
|
232
|
+
input.criteriaInstance.data?.filters || [];
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* Evaluate every metric-value filter against every series. We only
|
|
236
|
+
* handle CheckOn.MetricValue for now — the criteria evaluator's
|
|
237
|
+
* other filter types don't carry series information.
|
|
238
|
+
*/
|
|
239
|
+
const metricFilters: Array<CriteriaFilter> = filters.filter(
|
|
240
|
+
(f: CriteriaFilter) => {
|
|
241
|
+
return f.checkOn === CheckOn.MetricValue;
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (metricFilters.length === 0) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* Key: fingerprint. Value: results of each metric filter for that
|
|
251
|
+
* series, tracked so we can apply FilterCondition.All/Any per series.
|
|
252
|
+
*/
|
|
253
|
+
const resultsByFingerprint: Map<
|
|
254
|
+
string,
|
|
255
|
+
{
|
|
256
|
+
labels: JSONObject;
|
|
257
|
+
filterResults: Array<MetricSeriesEvaluationResult>;
|
|
258
|
+
}
|
|
259
|
+
> = new Map();
|
|
260
|
+
|
|
261
|
+
for (const criteriaFilter of metricFilters) {
|
|
262
|
+
const evaluations: Array<MetricSeriesEvaluationResult> =
|
|
263
|
+
MetricMonitorCriteria.evaluateAllSeries({
|
|
264
|
+
dataToProcess: input.dataToProcess,
|
|
265
|
+
criteriaFilter,
|
|
266
|
+
monitorStep: input.monitorStep,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
for (const evaluation of evaluations) {
|
|
270
|
+
const fingerprint: string | undefined = evaluation.fingerprint;
|
|
271
|
+
if (!fingerprint) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const existing:
|
|
276
|
+
| {
|
|
277
|
+
labels: JSONObject;
|
|
278
|
+
filterResults: Array<MetricSeriesEvaluationResult>;
|
|
279
|
+
}
|
|
280
|
+
| undefined = resultsByFingerprint.get(fingerprint);
|
|
281
|
+
|
|
282
|
+
if (existing) {
|
|
283
|
+
existing.filterResults.push(evaluation);
|
|
284
|
+
} else {
|
|
285
|
+
resultsByFingerprint.set(fingerprint, {
|
|
286
|
+
labels: evaluation.labels,
|
|
287
|
+
filterResults: [evaluation],
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const matches: Array<PerSeriesCriteriaMatch> = [];
|
|
294
|
+
|
|
295
|
+
for (const [fingerprint, entry] of resultsByFingerprint) {
|
|
296
|
+
// Were all/any of the metric filters met for this series?
|
|
297
|
+
const matched: Array<MetricSeriesEvaluationResult> =
|
|
298
|
+
entry.filterResults.filter((r: MetricSeriesEvaluationResult) => {
|
|
299
|
+
return r.rootCause !== null;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const seriesMatched: boolean =
|
|
303
|
+
filterCondition === FilterCondition.All
|
|
304
|
+
? matched.length === entry.filterResults.length &&
|
|
305
|
+
entry.filterResults.length > 0
|
|
306
|
+
: matched.length > 0;
|
|
307
|
+
|
|
308
|
+
if (!seriesMatched) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* For the per-series rootCause, concatenate the matched filter
|
|
314
|
+
* messages so the incident message reflects exactly what
|
|
315
|
+
* breached on this specific series.
|
|
316
|
+
*/
|
|
317
|
+
const rootCauseLines: Array<string> = matched.map(
|
|
318
|
+
(r: MetricSeriesEvaluationResult) => {
|
|
319
|
+
return `- ${r.rootCause}`;
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
matches.push({
|
|
324
|
+
criteriaMetId: criteriaId,
|
|
325
|
+
fingerprint,
|
|
326
|
+
labels: entry.labels,
|
|
327
|
+
rootCause: rootCauseLines.join("\n"),
|
|
328
|
+
metricContext: matched[0]?.context,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return matches;
|
|
333
|
+
}
|
|
334
|
+
|
|
166
335
|
private static async processMonitorCriteriaInstance(input: {
|
|
167
336
|
dataToProcess: DataToProcess;
|
|
168
337
|
monitorStep: MonitorStep;
|
|
@@ -29,6 +29,7 @@ import { JSONObject } from "../../../Types/JSON";
|
|
|
29
29
|
import OneUptimeDate from "../../../Types/Date";
|
|
30
30
|
import MonitorEvaluationSummary from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
31
31
|
import { IncidentMemberRoleAssignment } from "../../../Types/Monitor/CriteriaIncident";
|
|
32
|
+
import { PerSeriesCriteriaMatch } from "../../../Types/Probe/ProbeApiIngestResponse";
|
|
32
33
|
|
|
33
34
|
export default class MonitorIncident {
|
|
34
35
|
@CaptureSpan()
|
|
@@ -41,6 +42,15 @@ export default class MonitorIncident {
|
|
|
41
42
|
criteriaInstance: MonitorCriteriaInstance | null;
|
|
42
43
|
dataToProcess: DataToProcess;
|
|
43
44
|
evaluationSummary?: MonitorEvaluationSummary | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* When set, the fingerprint set of series still breaching on this
|
|
47
|
+
* tick. Any open per-series incident whose fingerprint is NOT in
|
|
48
|
+
* this set is auto-resolved — that's how a series returning to
|
|
49
|
+
* normal closes its incident independently of other series on the
|
|
50
|
+
* same monitor. Undefined means "legacy mode" and per-series
|
|
51
|
+
* incidents are treated like any other for dedupe/resolve.
|
|
52
|
+
*/
|
|
53
|
+
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
44
54
|
}): Promise<Array<Incident>> {
|
|
45
55
|
// check active incidents and if there are open incidents, do not cretae anothr incident.
|
|
46
56
|
const openIncidents: Array<Incident> = await IncidentService.findBy({
|
|
@@ -54,12 +64,15 @@ export default class MonitorIncident {
|
|
|
54
64
|
limit: LIMIT_PER_PROJECT,
|
|
55
65
|
select: {
|
|
56
66
|
_id: true,
|
|
67
|
+
title: true,
|
|
57
68
|
createdCriteriaId: true,
|
|
58
69
|
createdIncidentTemplateId: true,
|
|
59
70
|
projectId: true,
|
|
60
71
|
incidentNumber: true,
|
|
61
72
|
incidentNumberWithPrefix: true,
|
|
62
73
|
currentIncidentStateId: true,
|
|
74
|
+
seriesFingerprint: true,
|
|
75
|
+
seriesLabels: true,
|
|
63
76
|
},
|
|
64
77
|
props: {
|
|
65
78
|
isRoot: true,
|
|
@@ -74,6 +87,7 @@ export default class MonitorIncident {
|
|
|
74
87
|
autoResolveCriteriaInstanceIdIncidentIdsDictionary:
|
|
75
88
|
input.autoResolveCriteriaInstanceIdIncidentIdsDictionary,
|
|
76
89
|
criteriaInstance: input.criteriaInstance,
|
|
90
|
+
breachingSeriesFingerprints: input.breachingSeriesFingerprints,
|
|
77
91
|
});
|
|
78
92
|
|
|
79
93
|
if (shouldClose) {
|
|
@@ -115,6 +129,13 @@ export default class MonitorIncident {
|
|
|
115
129
|
props: {
|
|
116
130
|
telemetryQuery?: TelemetryQuery | undefined;
|
|
117
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* When set, create one incident per series instead of one per
|
|
134
|
+
* monitor. Each entry gets its own rootCause, seriesFingerprint,
|
|
135
|
+
* and seriesLabels so the incident title + description can
|
|
136
|
+
* reference `{{host.name}}` etc. via the template engine.
|
|
137
|
+
*/
|
|
138
|
+
matchesPerSeries?: Array<PerSeriesCriteriaMatch> | undefined;
|
|
118
139
|
}): Promise<void> {
|
|
119
140
|
const incidentLogAttributes: LogAttributes = {
|
|
120
141
|
projectId: input.monitor.projectId?.toString(),
|
|
@@ -125,6 +146,21 @@ export default class MonitorIncident {
|
|
|
125
146
|
`${input.monitor.id?.toString()} - Check open incidents.`,
|
|
126
147
|
incidentLogAttributes,
|
|
127
148
|
);
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* Per-series mode: close any open incident for a series that's no
|
|
152
|
+
* longer breaching *before* we look at the remaining open set, so
|
|
153
|
+
* dedupe decisions below match the post-resolve state.
|
|
154
|
+
*/
|
|
155
|
+
const breachingSeriesFingerprints: Set<string> | undefined =
|
|
156
|
+
input.matchesPerSeries
|
|
157
|
+
? new Set<string>(
|
|
158
|
+
input.matchesPerSeries.map((m: PerSeriesCriteriaMatch) => {
|
|
159
|
+
return m.fingerprint;
|
|
160
|
+
}),
|
|
161
|
+
)
|
|
162
|
+
: undefined;
|
|
163
|
+
|
|
128
164
|
// check active incidents and if there are open incidents, do not cretae anothr incident.
|
|
129
165
|
const openIncidents: Array<Incident> =
|
|
130
166
|
await this.checkOpenIncidentsAndCloseIfResolved({
|
|
@@ -135,14 +171,29 @@ export default class MonitorIncident {
|
|
|
135
171
|
criteriaInstance: input.criteriaInstance,
|
|
136
172
|
dataToProcess: input.dataToProcess,
|
|
137
173
|
evaluationSummary: input.evaluationSummary,
|
|
174
|
+
breachingSeriesFingerprints,
|
|
138
175
|
});
|
|
139
176
|
|
|
140
|
-
if (input.criteriaInstance.data?.createIncidents) {
|
|
141
|
-
|
|
177
|
+
if (!input.criteriaInstance.data?.createIncidents) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
142
180
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
/*
|
|
182
|
+
* Series-less path: one incident per criteriaIncident template as
|
|
183
|
+
* before. Series-aware path: one incident per (series × template).
|
|
184
|
+
*/
|
|
185
|
+
const seriesToProcess: Array<PerSeriesCriteriaMatch | undefined> =
|
|
186
|
+
input.matchesPerSeries && input.matchesPerSeries.length > 0
|
|
187
|
+
? input.matchesPerSeries
|
|
188
|
+
: [undefined];
|
|
189
|
+
|
|
190
|
+
for (const criteriaIncident of input.criteriaInstance.data?.incidents ||
|
|
191
|
+
[]) {
|
|
192
|
+
for (const seriesMatch of seriesToProcess) {
|
|
193
|
+
const seriesFingerprint: string | undefined = seriesMatch?.fingerprint;
|
|
194
|
+
const seriesLabels: JSONObject | undefined = seriesMatch?.labels;
|
|
195
|
+
const seriesRootCause: string =
|
|
196
|
+
seriesMatch?.rootCause || input.rootCause;
|
|
146
197
|
|
|
147
198
|
const alreadyOpenIncident: Incident | undefined = openIncidents.find(
|
|
148
199
|
(incident: Incident) => {
|
|
@@ -150,7 +201,8 @@ export default class MonitorIncident {
|
|
|
150
201
|
incident.createdCriteriaId ===
|
|
151
202
|
input.criteriaInstance.data?.id.toString() &&
|
|
152
203
|
incident.createdIncidentTemplateId ===
|
|
153
|
-
criteriaIncident.id.toString()
|
|
204
|
+
criteriaIncident.id.toString() &&
|
|
205
|
+
(incident.seriesFingerprint || undefined) === seriesFingerprint
|
|
154
206
|
);
|
|
155
207
|
},
|
|
156
208
|
);
|
|
@@ -168,9 +220,20 @@ export default class MonitorIncident {
|
|
|
168
220
|
);
|
|
169
221
|
|
|
170
222
|
if (hasAlreadyOpenIncident) {
|
|
223
|
+
/*
|
|
224
|
+
* Use the open incident's already-rendered title when
|
|
225
|
+
* available — the template (`criteriaIncident.title`) still
|
|
226
|
+
* contains unresolved `{{…}}` placeholders because it's the
|
|
227
|
+
* criterion's template string, not the instance's rendered
|
|
228
|
+
* output. Falling back to the template only when the open
|
|
229
|
+
* incident somehow has no title.
|
|
230
|
+
*/
|
|
231
|
+
const renderedTitle: string =
|
|
232
|
+
alreadyOpenIncident?.title || criteriaIncident.title;
|
|
233
|
+
|
|
171
234
|
input.evaluationSummary?.events.push({
|
|
172
235
|
type: "incident-skipped",
|
|
173
|
-
title: `Incident already active: ${
|
|
236
|
+
title: `Incident already active: ${renderedTitle}`,
|
|
174
237
|
message:
|
|
175
238
|
"Skipped creating a new incident because an active incident exists for this criteria.",
|
|
176
239
|
relatedCriteriaId: input.criteriaInstance.data?.id,
|
|
@@ -193,6 +256,8 @@ export default class MonitorIncident {
|
|
|
193
256
|
MonitorTemplateUtil.buildTemplateStorageMap({
|
|
194
257
|
monitorType: input.monitor.monitorType!,
|
|
195
258
|
dataToProcess: input.dataToProcess,
|
|
259
|
+
monitor: input.monitor,
|
|
260
|
+
seriesLabels,
|
|
196
261
|
});
|
|
197
262
|
|
|
198
263
|
incident.title = MonitorTemplateUtil.processTemplateString({
|
|
@@ -236,7 +301,7 @@ export default class MonitorIncident {
|
|
|
236
301
|
|
|
237
302
|
incident.monitors = [input.monitor];
|
|
238
303
|
incident.projectId = input.monitor.projectId!;
|
|
239
|
-
incident.rootCause =
|
|
304
|
+
incident.rootCause = seriesRootCause;
|
|
240
305
|
incident.createdStateLog = JSON.parse(
|
|
241
306
|
JSON.stringify(input.dataToProcess, null, 2),
|
|
242
307
|
);
|
|
@@ -245,6 +310,13 @@ export default class MonitorIncident {
|
|
|
245
310
|
|
|
246
311
|
incident.createdIncidentTemplateId = criteriaIncident.id.toString();
|
|
247
312
|
|
|
313
|
+
if (seriesFingerprint) {
|
|
314
|
+
incident.seriesFingerprint = seriesFingerprint;
|
|
315
|
+
}
|
|
316
|
+
if (seriesLabels && Object.keys(seriesLabels).length > 0) {
|
|
317
|
+
incident.seriesLabels = seriesLabels;
|
|
318
|
+
}
|
|
319
|
+
|
|
248
320
|
incident.onCallDutyPolicies =
|
|
249
321
|
criteriaIncident.onCallPolicyIds?.map((id: ObjectID) => {
|
|
250
322
|
const onCallPolicy: OnCallDutyPolicy = new OnCallDutyPolicy();
|
|
@@ -504,7 +576,60 @@ export default class MonitorIncident {
|
|
|
504
576
|
Array<string>
|
|
505
577
|
>;
|
|
506
578
|
criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met.
|
|
579
|
+
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
507
580
|
}): boolean {
|
|
581
|
+
const openSeriesFingerprint: string | undefined =
|
|
582
|
+
input.openIncident.seriesFingerprint || undefined;
|
|
583
|
+
|
|
584
|
+
/*
|
|
585
|
+
* Per-series auto-resolve: when the monitor emits a breaching-
|
|
586
|
+
* series set and this open incident has a fingerprint, resolve
|
|
587
|
+
* whenever the fingerprint is no longer in the set — regardless
|
|
588
|
+
* of whether some *other* series is still breaching the same
|
|
589
|
+
* criteria. This is the whole point of per-host incidents.
|
|
590
|
+
*/
|
|
591
|
+
if (
|
|
592
|
+
input.breachingSeriesFingerprints !== undefined &&
|
|
593
|
+
openSeriesFingerprint
|
|
594
|
+
) {
|
|
595
|
+
const stillBreaching: boolean = input.breachingSeriesFingerprints.has(
|
|
596
|
+
openSeriesFingerprint,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
if (stillBreaching) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/*
|
|
604
|
+
* Series no longer breaching. Only auto-close if the criteria
|
|
605
|
+
* was configured to auto-resolve in the first place; otherwise
|
|
606
|
+
* stay open so a human can acknowledge.
|
|
607
|
+
*/
|
|
608
|
+
if (!input.openIncident.createdCriteriaId?.toString()) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (!input.openIncident.createdIncidentTemplateId?.toString()) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const autoResolveTemplates: Array<string> | undefined =
|
|
617
|
+
input.autoResolveCriteriaInstanceIdIncidentIdsDictionary[
|
|
618
|
+
input.openIncident.createdCriteriaId.toString()
|
|
619
|
+
];
|
|
620
|
+
|
|
621
|
+
if (
|
|
622
|
+
autoResolveTemplates &&
|
|
623
|
+
autoResolveTemplates.includes(
|
|
624
|
+
input.openIncident.createdIncidentTemplateId.toString(),
|
|
625
|
+
)
|
|
626
|
+
) {
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
508
633
|
if (
|
|
509
634
|
input.openIncident.createdCriteriaId?.toString() ===
|
|
510
635
|
input.criteriaInstance?.data?.id.toString()
|