@oneuptime/common 10.0.70 → 10.0.72

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.
Files changed (144) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/Incident.ts +55 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +6 -4
  4. package/Models/DatabaseModels/Project.ts +5 -5
  5. package/Models/DatabaseModels/StatusPage.ts +80 -0
  6. package/Server/API/StatusPageAPI.ts +4 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.ts +6 -3
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.ts +17 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.ts +41 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.ts +25 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  12. package/Server/Services/AIBillingService.ts +2 -2
  13. package/Server/Services/AnalyticsDatabaseService.ts +17 -7
  14. package/Server/Services/BillingService.ts +116 -48
  15. package/Server/Services/NotificationService.ts +2 -2
  16. package/Server/Types/Database/QueryUtil.ts +13 -7
  17. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +175 -29
  18. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +71 -0
  19. package/Server/Utils/Monitor/MonitorAlert.ts +170 -7
  20. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +171 -2
  21. package/Server/Utils/Monitor/MonitorIncident.ts +212 -8
  22. package/Server/Utils/Monitor/MonitorMetricUtil.ts +423 -1
  23. package/Server/Utils/Monitor/MonitorResource.ts +2 -0
  24. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +99 -0
  25. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +268 -0
  26. package/Types/BaseDatabase/IncludesNone.ts +1 -4
  27. package/Types/Email.ts +50 -0
  28. package/Types/Infrastructure/BasicMetrics.ts +75 -0
  29. package/Types/Metrics/MetricQueryData.ts +11 -0
  30. package/Types/Monitor/CriteriaFilter.ts +10 -0
  31. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +11 -0
  32. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +10 -0
  33. package/Types/Monitor/MetricMonitor/MetricSeriesResult.ts +20 -0
  34. package/Types/Monitor/MonitorMetricType.ts +34 -0
  35. package/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts +8 -0
  36. package/Types/Probe/ProbeApiIngestResponse.ts +25 -0
  37. package/Types/StatusPage/StatusPageLanguage.ts +29 -0
  38. package/UI/Components/Charts/Area/AreaChart.tsx +17 -12
  39. package/UI/Components/Charts/Bar/BarChart.tsx +16 -11
  40. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -0
  41. package/UI/Components/Charts/Line/LineChart.tsx +16 -11
  42. package/UI/Components/Filters/DateFilter.tsx +16 -8
  43. package/UI/Components/Filters/EntityFilter.tsx +33 -18
  44. package/UI/Components/Filters/FilterViewer.tsx +7 -5
  45. package/UI/Components/Filters/FiltersForm.tsx +27 -5
  46. package/UI/Components/Filters/NumberFilter.tsx +3 -2
  47. package/UI/Components/Filters/TextFilter.tsx +5 -4
  48. package/UI/Components/ModelTable/BaseModelTable.tsx +5 -3
  49. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +453 -0
  50. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +229 -0
  51. package/Utils/Metrics/MetricSeriesFingerprint.ts +97 -0
  52. package/Utils/Monitor/MonitorMetricType.ts +309 -19
  53. package/build/dist/Models/DatabaseModels/Alert.js +57 -0
  54. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  55. package/build/dist/Models/DatabaseModels/Incident.js +57 -0
  56. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  57. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +6 -4
  58. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  59. package/build/dist/Models/DatabaseModels/Project.js +5 -5
  60. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  61. package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
  62. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  63. package/build/dist/Server/API/StatusPageAPI.js +4 -0
  64. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  65. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.js +4 -2
  66. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.js.map +1 -1
  67. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js +12 -0
  68. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js.map +1 -0
  69. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js +22 -0
  70. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js.map +1 -0
  71. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js +14 -0
  72. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js.map +1 -0
  73. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  75. package/build/dist/Server/Services/AIBillingService.js +2 -2
  76. package/build/dist/Server/Services/AIBillingService.js.map +1 -1
  77. package/build/dist/Server/Services/AnalyticsDatabaseService.js +14 -4
  78. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  79. package/build/dist/Server/Services/BillingService.js +99 -39
  80. package/build/dist/Server/Services/BillingService.js.map +1 -1
  81. package/build/dist/Server/Services/NotificationService.js +2 -2
  82. package/build/dist/Server/Services/NotificationService.js.map +1 -1
  83. package/build/dist/Server/Types/Database/QueryUtil.js +13 -7
  84. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  85. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +132 -30
  86. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  87. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +58 -7
  88. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
  89. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +134 -12
  90. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  91. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +112 -0
  92. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  93. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +159 -15
  94. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  95. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +373 -0
  96. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  97. package/build/dist/Server/Utils/Monitor/MonitorResource.js +2 -0
  98. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  99. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +65 -0
  100. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  101. package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js +199 -0
  102. package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js.map +1 -1
  103. package/build/dist/Types/BaseDatabase/IncludesNone.js.map +1 -1
  104. package/build/dist/Types/Email.js +42 -0
  105. package/build/dist/Types/Email.js.map +1 -1
  106. package/build/dist/Types/Monitor/CriteriaFilter.js +10 -0
  107. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  108. package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js +2 -0
  109. package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js.map +1 -0
  110. package/build/dist/Types/Monitor/MonitorMetricType.js +28 -0
  111. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  112. package/build/dist/Types/StatusPage/StatusPageLanguage.js +21 -0
  113. package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -0
  114. package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -12
  115. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  116. package/build/dist/UI/Components/Charts/Bar/BarChart.js +12 -11
  117. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  118. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +11 -3
  119. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  120. package/build/dist/UI/Components/Charts/Line/LineChart.js +12 -11
  121. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  122. package/build/dist/UI/Components/Filters/DateFilter.js +1 -4
  123. package/build/dist/UI/Components/Filters/DateFilter.js.map +1 -1
  124. package/build/dist/UI/Components/Filters/EntityFilter.js +21 -14
  125. package/build/dist/UI/Components/Filters/EntityFilter.js.map +1 -1
  126. package/build/dist/UI/Components/Filters/FilterViewer.js +1 -2
  127. package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
  128. package/build/dist/UI/Components/Filters/FiltersForm.js +7 -3
  129. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  130. package/build/dist/UI/Components/Filters/NumberFilter.js +0 -1
  131. package/build/dist/UI/Components/Filters/NumberFilter.js.map +1 -1
  132. package/build/dist/UI/Components/Filters/TextFilter.js +5 -4
  133. package/build/dist/UI/Components/Filters/TextFilter.js.map +1 -1
  134. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +5 -3
  135. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  136. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +383 -0
  137. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -0
  138. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +109 -0
  139. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js.map +1 -0
  140. package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js +81 -0
  141. package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js.map +1 -0
  142. package/build/dist/Utils/Monitor/MonitorMetricType.js +287 -19
  143. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  144. package/package.json +1 -1
@@ -17,8 +17,10 @@ import { TelemetryQuery } from "../../../Types/Telemetry/TelemetryQuery";
17
17
  import { DisableAutomaticIncidentCreation } from "../../EnvironmentConfig";
18
18
  import IncidentService from "../../Services/IncidentService";
19
19
  import IncidentSeverityService from "../../Services/IncidentSeverityService";
20
+ import IncidentStateService from "../../Services/IncidentStateService";
20
21
  import IncidentStateTimelineService from "../../Services/IncidentStateTimelineService";
21
22
  import IncidentMemberService from "../../Services/IncidentMemberService";
23
+ import IncidentState from "../../../Models/DatabaseModels/IncidentState";
22
24
  import logger, { LogAttributes } from "../Logger";
23
25
  import CaptureSpan from "../Telemetry/CaptureSpan";
24
26
  import DataToProcess from "./DataToProcess";
@@ -27,6 +29,7 @@ import { JSONObject } from "../../../Types/JSON";
27
29
  import OneUptimeDate from "../../../Types/Date";
28
30
  import MonitorEvaluationSummary from "../../../Types/Monitor/MonitorEvaluationSummary";
29
31
  import { IncidentMemberRoleAssignment } from "../../../Types/Monitor/CriteriaIncident";
32
+ import { PerSeriesCriteriaMatch } from "../../../Types/Probe/ProbeApiIngestResponse";
30
33
 
31
34
  export default class MonitorIncident {
32
35
  @CaptureSpan()
@@ -39,6 +42,15 @@ export default class MonitorIncident {
39
42
  criteriaInstance: MonitorCriteriaInstance | null;
40
43
  dataToProcess: DataToProcess;
41
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;
42
54
  }): Promise<Array<Incident>> {
43
55
  // check active incidents and if there are open incidents, do not cretae anothr incident.
44
56
  const openIncidents: Array<Incident> = await IncidentService.findBy({
@@ -52,11 +64,15 @@ export default class MonitorIncident {
52
64
  limit: LIMIT_PER_PROJECT,
53
65
  select: {
54
66
  _id: true,
67
+ title: true,
55
68
  createdCriteriaId: true,
56
69
  createdIncidentTemplateId: true,
57
70
  projectId: true,
58
71
  incidentNumber: true,
59
72
  incidentNumberWithPrefix: true,
73
+ currentIncidentStateId: true,
74
+ seriesFingerprint: true,
75
+ seriesLabels: true,
60
76
  },
61
77
  props: {
62
78
  isRoot: true,
@@ -71,6 +87,7 @@ export default class MonitorIncident {
71
87
  autoResolveCriteriaInstanceIdIncidentIdsDictionary:
72
88
  input.autoResolveCriteriaInstanceIdIncidentIdsDictionary,
73
89
  criteriaInstance: input.criteriaInstance,
90
+ breachingSeriesFingerprints: input.breachingSeriesFingerprints,
74
91
  });
75
92
 
76
93
  if (shouldClose) {
@@ -112,6 +129,13 @@ export default class MonitorIncident {
112
129
  props: {
113
130
  telemetryQuery?: TelemetryQuery | undefined;
114
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;
115
139
  }): Promise<void> {
116
140
  const incidentLogAttributes: LogAttributes = {
117
141
  projectId: input.monitor.projectId?.toString(),
@@ -122,6 +146,21 @@ export default class MonitorIncident {
122
146
  `${input.monitor.id?.toString()} - Check open incidents.`,
123
147
  incidentLogAttributes,
124
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
+
125
164
  // check active incidents and if there are open incidents, do not cretae anothr incident.
126
165
  const openIncidents: Array<Incident> =
127
166
  await this.checkOpenIncidentsAndCloseIfResolved({
@@ -132,14 +171,29 @@ export default class MonitorIncident {
132
171
  criteriaInstance: input.criteriaInstance,
133
172
  dataToProcess: input.dataToProcess,
134
173
  evaluationSummary: input.evaluationSummary,
174
+ breachingSeriesFingerprints,
135
175
  });
136
176
 
137
- if (input.criteriaInstance.data?.createIncidents) {
138
- // create incidents
177
+ if (!input.criteriaInstance.data?.createIncidents) {
178
+ return;
179
+ }
139
180
 
140
- for (const criteriaIncident of input.criteriaInstance.data?.incidents ||
141
- []) {
142
- // should create incident.
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;
143
197
 
144
198
  const alreadyOpenIncident: Incident | undefined = openIncidents.find(
145
199
  (incident: Incident) => {
@@ -147,7 +201,8 @@ export default class MonitorIncident {
147
201
  incident.createdCriteriaId ===
148
202
  input.criteriaInstance.data?.id.toString() &&
149
203
  incident.createdIncidentTemplateId ===
150
- criteriaIncident.id.toString()
204
+ criteriaIncident.id.toString() &&
205
+ (incident.seriesFingerprint || undefined) === seriesFingerprint
151
206
  );
152
207
  },
153
208
  );
@@ -165,9 +220,20 @@ export default class MonitorIncident {
165
220
  );
166
221
 
167
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
+
168
234
  input.evaluationSummary?.events.push({
169
235
  type: "incident-skipped",
170
- title: `Incident already active: ${criteriaIncident.title}`,
236
+ title: `Incident already active: ${renderedTitle}`,
171
237
  message:
172
238
  "Skipped creating a new incident because an active incident exists for this criteria.",
173
239
  relatedCriteriaId: input.criteriaInstance.data?.id,
@@ -190,6 +256,8 @@ export default class MonitorIncident {
190
256
  MonitorTemplateUtil.buildTemplateStorageMap({
191
257
  monitorType: input.monitor.monitorType!,
192
258
  dataToProcess: input.dataToProcess,
259
+ monitor: input.monitor,
260
+ seriesLabels,
193
261
  });
194
262
 
195
263
  incident.title = MonitorTemplateUtil.processTemplateString({
@@ -233,7 +301,7 @@ export default class MonitorIncident {
233
301
 
234
302
  incident.monitors = [input.monitor];
235
303
  incident.projectId = input.monitor.projectId!;
236
- incident.rootCause = input.rootCause;
304
+ incident.rootCause = seriesRootCause;
237
305
  incident.createdStateLog = JSON.parse(
238
306
  JSON.stringify(input.dataToProcess, null, 2),
239
307
  );
@@ -242,6 +310,13 @@ export default class MonitorIncident {
242
310
 
243
311
  incident.createdIncidentTemplateId = criteriaIncident.id.toString();
244
312
 
313
+ if (seriesFingerprint) {
314
+ incident.seriesFingerprint = seriesFingerprint;
315
+ }
316
+ if (seriesLabels && Object.keys(seriesLabels).length > 0) {
317
+ incident.seriesLabels = seriesLabels;
318
+ }
319
+
245
320
  incident.onCallDutyPolicies =
246
321
  criteriaIncident.onCallPolicyIds?.map((id: ObjectID) => {
247
322
  const onCallPolicy: OnCallDutyPolicy = new OnCallDutyPolicy();
@@ -393,6 +468,82 @@ export default class MonitorIncident {
393
468
  input.openIncident.projectId!,
394
469
  );
395
470
 
471
+ /*
472
+ * Skip the Resolved insert if the incident's timeline is already at or
473
+ * past the Resolved state in the project's workflow order. Two cases:
474
+ * 1. Latest timeline state is Resolved but Incident.currentIncidentStateId
475
+ * is stuck on an earlier state (partial-failure from a prior resolve).
476
+ * Re-inserting Resolved would throw "state cannot be same as previous"
477
+ * from IncidentStateTimelineService.onBeforeCreate.
478
+ * 2. The project defines a custom state after Resolved (e.g. Closed) and
479
+ * the incident has moved into it. Inserting Resolved would throw
480
+ * "cannot transition to Resolved from Closed because Resolved is
481
+ * before Closed in the order of incident states."
482
+ * Either failure bubbles up through ingest workers and causes monitors to
483
+ * flap. Reconcile Incident.currentIncidentStateId if out of sync with the
484
+ * timeline, then return.
485
+ */
486
+ const [resolvedState, latestTimeline]: [
487
+ IncidentState | null,
488
+ IncidentStateTimeline | null,
489
+ ] = await Promise.all([
490
+ IncidentStateService.findOneBy({
491
+ query: {
492
+ _id: resolvedStateId.toString(),
493
+ },
494
+ select: {
495
+ order: true,
496
+ },
497
+ props: {
498
+ isRoot: true,
499
+ },
500
+ }),
501
+ IncidentStateTimelineService.findOneBy({
502
+ query: {
503
+ incidentId: input.openIncident.id!,
504
+ },
505
+ sort: {
506
+ startsAt: SortOrder.Descending,
507
+ },
508
+ select: {
509
+ incidentStateId: true,
510
+ incidentState: {
511
+ order: true,
512
+ },
513
+ },
514
+ props: {
515
+ isRoot: true,
516
+ },
517
+ }),
518
+ ]);
519
+
520
+ const latestOrder: number | undefined | null =
521
+ latestTimeline?.incidentState?.order;
522
+ const resolvedOrder: number | undefined | null = resolvedState?.order;
523
+
524
+ if (
525
+ latestTimeline?.incidentStateId &&
526
+ typeof latestOrder === "number" &&
527
+ typeof resolvedOrder === "number" &&
528
+ latestOrder >= resolvedOrder
529
+ ) {
530
+ if (
531
+ input.openIncident.currentIncidentStateId?.toString() !==
532
+ latestTimeline.incidentStateId.toString()
533
+ ) {
534
+ await IncidentService.updateOneById({
535
+ id: input.openIncident.id!,
536
+ data: {
537
+ currentIncidentStateId: latestTimeline.incidentStateId,
538
+ },
539
+ props: {
540
+ isRoot: true,
541
+ },
542
+ });
543
+ }
544
+ return;
545
+ }
546
+
396
547
  const incidentStateTimeline: IncidentStateTimeline =
397
548
  new IncidentStateTimeline();
398
549
  incidentStateTimeline.incidentId = input.openIncident.id!;
@@ -425,7 +576,60 @@ export default class MonitorIncident {
425
576
  Array<string>
426
577
  >;
427
578
  criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met.
579
+ breachingSeriesFingerprints?: Set<string> | undefined;
428
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
+
429
633
  if (
430
634
  input.openIncident.createdCriteriaId?.toString() ===
431
635
  input.criteriaInstance?.data?.id.toString()