@oneuptime/common 11.3.5 → 11.3.6
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/Server/Infrastructure/Queue.ts +103 -16
- package/Server/Services/AnalyticsDatabaseService.ts +9 -5
- package/Server/Services/TeamMemberService.ts +38 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +31 -0
- package/Server/Utils/Monitor/IncomingRequestIncidentGrouping.ts +428 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +158 -8
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +26 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +147 -2
- package/Server/Utils/Monitor/MonitorResource.ts +74 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +2 -1
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +32 -0
- package/Tests/Server/Utils/Monitor/IncomingRequestIncidentGrouping.test.ts +389 -0
- package/Tests/Server/Utils/Monitor/SeriesAbsenceResolutionGuard.test.ts +177 -0
- package/Tests/Types/Events/Recurring.test.ts +475 -0
- package/Tests/Types/Monitor/CriteriaFilter.test.ts +348 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +331 -0
- package/Tests/UI/Components/Alert.test.tsx +23 -15
- package/Tests/UI/Components/Breadcrumbs.test.tsx +18 -6
- package/Tests/UI/Components/Button.test.tsx +3 -3
- package/Tests/UI/Components/DictionaryFilterOperator.test.ts +174 -0
- package/Tests/UI/Components/EmptyState/EmptyState.test.tsx +4 -3
- package/Tests/UI/Components/Input.test.tsx +3 -2
- package/Tests/UI/Components/Pill.test.tsx +4 -2
- package/Tests/UI/Components/SideMenuItem.test.tsx +18 -8
- package/Tests/UI/Components/TextArea.test.tsx +6 -4
- package/Types/Monitor/IncomingMonitor/IncidentGroupingConfig.ts +74 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +22 -0
- package/UI/Components/Dictionary/Dictionary.tsx +123 -50
- package/UI/Components/Dictionary/DictionaryFilterOperator.ts +96 -1
- package/UI/Components/MasterPage/MasterPage.tsx +5 -3
- package/build/dist/Server/Infrastructure/Queue.js +83 -15
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +9 -5
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +37 -1
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +26 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/IncomingRequestIncidentGrouping.js +288 -0
- package/build/dist/Server/Utils/Monitor/IncomingRequestIncidentGrouping.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +119 -8
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +24 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +103 -2
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +62 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Types/Monitor/IncomingMonitor/IncidentGroupingConfig.js +8 -0
- package/build/dist/Types/Monitor/IncomingMonitor/IncidentGroupingConfig.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +10 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +60 -9
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +69 -0
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +5 -3
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -72,6 +72,14 @@ export default class MonitorIncident {
|
|
|
72
72
|
* incidents are treated like any other for dedupe/resolve.
|
|
73
73
|
*/
|
|
74
74
|
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Event-driven (incoming-request / webhook) mode. When true, an open
|
|
77
|
+
* incident carrying a seriesFingerprint is never auto-resolved here —
|
|
78
|
+
* webhooks resolve per-key only via resolveSeriesIncidentsByFingerprint,
|
|
79
|
+
* never by absence. Must be passed on BOTH the criteria-met and the
|
|
80
|
+
* no-criteria-met code paths for grouped incoming-request monitors.
|
|
81
|
+
*/
|
|
82
|
+
disableSeriesAbsenceResolution?: boolean | undefined;
|
|
75
83
|
}): Promise<Array<Incident>> {
|
|
76
84
|
// check active incidents and if there are open incidents, do not create another incident.
|
|
77
85
|
const openIncidents: Array<Incident> = await IncidentService.findBy({
|
|
@@ -109,6 +117,7 @@ export default class MonitorIncident {
|
|
|
109
117
|
input.autoResolveCriteriaInstanceIdIncidentIdsDictionary,
|
|
110
118
|
criteriaInstance: input.criteriaInstance,
|
|
111
119
|
breachingSeriesFingerprints: input.breachingSeriesFingerprints,
|
|
120
|
+
disableSeriesAbsenceResolution: input.disableSeriesAbsenceResolution,
|
|
112
121
|
});
|
|
113
122
|
|
|
114
123
|
if (shouldClose) {
|
|
@@ -137,6 +146,107 @@ export default class MonitorIncident {
|
|
|
137
146
|
return openIncidents;
|
|
138
147
|
}
|
|
139
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Event-driven (incoming-request / webhook) resolution: resolve the
|
|
151
|
+
* open incidents for the given payload-derived fingerprints — and only
|
|
152
|
+
* those — when the criteria that created them has auto-resolve enabled.
|
|
153
|
+
*
|
|
154
|
+
* Unlike the metric snapshot model, this never resolves an incident by
|
|
155
|
+
* absence: a webhook describes only the keys in its payload, so a
|
|
156
|
+
* missing key is not a recovery signal. The caller passes exactly the
|
|
157
|
+
* fingerprints the payload explicitly classified as resolved (see
|
|
158
|
+
* IncomingRequestIncidentGrouping.collectResolvedFingerprints).
|
|
159
|
+
*/
|
|
160
|
+
@CaptureSpan()
|
|
161
|
+
public static async resolveSeriesIncidentsByFingerprint(input: {
|
|
162
|
+
monitor: Monitor;
|
|
163
|
+
fingerprints: Array<string>;
|
|
164
|
+
rootCause: string;
|
|
165
|
+
dataToProcess: DataToProcess;
|
|
166
|
+
autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary<
|
|
167
|
+
Array<string>
|
|
168
|
+
>;
|
|
169
|
+
evaluationSummary?: MonitorEvaluationSummary | undefined;
|
|
170
|
+
}): Promise<void> {
|
|
171
|
+
if (!input.fingerprints || input.fingerprints.length === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const fingerprintSet: Set<string> = new Set<string>(input.fingerprints);
|
|
176
|
+
|
|
177
|
+
const openIncidents: Array<Incident> = await IncidentService.findBy({
|
|
178
|
+
query: {
|
|
179
|
+
monitors: [input.monitor.id!],
|
|
180
|
+
currentIncidentState: {
|
|
181
|
+
isResolvedState: false,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
skip: 0,
|
|
185
|
+
limit: LIMIT_PER_PROJECT,
|
|
186
|
+
select: {
|
|
187
|
+
_id: true,
|
|
188
|
+
title: true,
|
|
189
|
+
createdCriteriaId: true,
|
|
190
|
+
createdIncidentTemplateId: true,
|
|
191
|
+
projectId: true,
|
|
192
|
+
incidentNumber: true,
|
|
193
|
+
incidentNumberWithPrefix: true,
|
|
194
|
+
seriesFingerprint: true,
|
|
195
|
+
},
|
|
196
|
+
props: {
|
|
197
|
+
isRoot: true,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
for (const openIncident of openIncidents) {
|
|
202
|
+
const fingerprint: string | undefined =
|
|
203
|
+
openIncident.seriesFingerprint || undefined;
|
|
204
|
+
|
|
205
|
+
if (!fingerprint || !fingerprintSet.has(fingerprint)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const createdCriteriaId: string | undefined =
|
|
210
|
+
openIncident.createdCriteriaId?.toString();
|
|
211
|
+
const createdIncidentTemplateId: string | undefined =
|
|
212
|
+
openIncident.createdIncidentTemplateId?.toString();
|
|
213
|
+
|
|
214
|
+
// Only auto-resolve when the creating criteria opted into it.
|
|
215
|
+
if (!createdCriteriaId || !createdIncidentTemplateId) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const autoResolveTemplates: Array<string> | undefined =
|
|
220
|
+
input.autoResolveCriteriaInstanceIdIncidentIdsDictionary[
|
|
221
|
+
createdCriteriaId
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
!autoResolveTemplates ||
|
|
226
|
+
!autoResolveTemplates.includes(createdIncidentTemplateId)
|
|
227
|
+
) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await this.resolveOpenIncident({
|
|
232
|
+
openIncident: openIncident,
|
|
233
|
+
rootCause: input.rootCause,
|
|
234
|
+
dataToProcess: input.dataToProcess,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
input.evaluationSummary?.events.push({
|
|
238
|
+
type: "incident-resolved",
|
|
239
|
+
title: `Incident resolved: ${openIncident.id?.toString()}`,
|
|
240
|
+
message:
|
|
241
|
+
"Incident auto-resolved because the incoming payload reported this key as resolved.",
|
|
242
|
+
relatedIncidentId: openIncident.id?.toString(),
|
|
243
|
+
relatedIncidentNumber: openIncident.incidentNumber,
|
|
244
|
+
relatedIncidentNumberWithPrefix: openIncident.incidentNumberWithPrefix,
|
|
245
|
+
at: OneUptimeDate.getCurrentDate(),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
140
250
|
@CaptureSpan()
|
|
141
251
|
public static async criteriaMetCreateIncidentsAndUpdateMonitorStatus(input: {
|
|
142
252
|
criteriaInstance: MonitorCriteriaInstance;
|
|
@@ -165,6 +275,17 @@ export default class MonitorIncident {
|
|
|
165
275
|
* are suppressed at creation time. See MonitorMaintenanceSuppression.
|
|
166
276
|
*/
|
|
167
277
|
suppressedSeriesFingerprints?: Set<string> | undefined;
|
|
278
|
+
/**
|
|
279
|
+
* Event-driven monitors (incoming-request / webhook fan-out) must not
|
|
280
|
+
* use the metric snapshot model where a series absent from this tick's
|
|
281
|
+
* breaching set is auto-resolved — a single webhook only describes the
|
|
282
|
+
* keys in that payload, not the full firing set, so absence is not
|
|
283
|
+
* recovery. When true, the per-series absence-resolve pass is skipped;
|
|
284
|
+
* those incidents are resolved explicitly elsewhere (see
|
|
285
|
+
* IncomingRequestIncidentGrouping + resolveSeriesIncidentsByFingerprint).
|
|
286
|
+
* Per-key create + dedupe still happen via matchesPerSeries.
|
|
287
|
+
*/
|
|
288
|
+
disableSeriesAbsenceResolution?: boolean | undefined;
|
|
168
289
|
}): Promise<void> {
|
|
169
290
|
const incidentLogAttributes: LogAttributes = {
|
|
170
291
|
projectId: input.monitor.projectId?.toString(),
|
|
@@ -182,7 +303,7 @@ export default class MonitorIncident {
|
|
|
182
303
|
* dedupe decisions below match the post-resolve state.
|
|
183
304
|
*/
|
|
184
305
|
const breachingSeriesFingerprints: Set<string> | undefined =
|
|
185
|
-
input.matchesPerSeries
|
|
306
|
+
input.matchesPerSeries && !input.disableSeriesAbsenceResolution
|
|
186
307
|
? new Set<string>(
|
|
187
308
|
input.matchesPerSeries.map((m: PerSeriesCriteriaMatch) => {
|
|
188
309
|
return m.fingerprint;
|
|
@@ -201,6 +322,7 @@ export default class MonitorIncident {
|
|
|
201
322
|
dataToProcess: input.dataToProcess,
|
|
202
323
|
evaluationSummary: input.evaluationSummary,
|
|
203
324
|
breachingSeriesFingerprints,
|
|
325
|
+
disableSeriesAbsenceResolution: input.disableSeriesAbsenceResolution,
|
|
204
326
|
});
|
|
205
327
|
|
|
206
328
|
if (!input.criteriaInstance.data?.createIncidents) {
|
|
@@ -224,8 +346,15 @@ export default class MonitorIncident {
|
|
|
224
346
|
* Series-less path: one incident per criteriaIncident template as
|
|
225
347
|
* before. Series-aware path: one incident per (series × template).
|
|
226
348
|
*/
|
|
349
|
+
/*
|
|
350
|
+
* `undefined` matchesPerSeries → legacy single-incident path. A
|
|
351
|
+
* defined (even empty) array → per-series mode: iterate exactly the
|
|
352
|
+
* matches. An empty array therefore creates nothing — used by grouped
|
|
353
|
+
* incoming-request criteria on a payload with no firing key so they
|
|
354
|
+
* don't fall back to a single whole-monitor incident.
|
|
355
|
+
*/
|
|
227
356
|
const seriesToProcess: Array<PerSeriesCriteriaMatch | undefined> =
|
|
228
|
-
input.matchesPerSeries
|
|
357
|
+
input.matchesPerSeries !== undefined
|
|
229
358
|
? input.matchesPerSeries
|
|
230
359
|
: [undefined];
|
|
231
360
|
|
|
@@ -981,10 +1110,26 @@ export default class MonitorIncident {
|
|
|
981
1110
|
>;
|
|
982
1111
|
criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met.
|
|
983
1112
|
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
1113
|
+
disableSeriesAbsenceResolution?: boolean | undefined;
|
|
984
1114
|
}): boolean {
|
|
985
1115
|
const openSeriesFingerprint: string | undefined =
|
|
986
1116
|
input.openIncident.seriesFingerprint || undefined;
|
|
987
1117
|
|
|
1118
|
+
/*
|
|
1119
|
+
* Event-driven (incoming-request / webhook) per-key incidents must
|
|
1120
|
+
* NEVER be resolved by absence — only explicitly, via
|
|
1121
|
+
* resolveSeriesIncidentsByFingerprint, when the payload reports the
|
|
1122
|
+
* key as recovered. A single webhook describes only the keys in its
|
|
1123
|
+
* own payload, so neither the per-series breaching-set path nor the
|
|
1124
|
+
* legacy cross-criteria path below may close a series incident here.
|
|
1125
|
+
* Without this guard, a heartbeat-timeout cron tick or a webhook that
|
|
1126
|
+
* the grouping criteria rejects would bulk-resolve all open per-key
|
|
1127
|
+
* incidents by absence.
|
|
1128
|
+
*/
|
|
1129
|
+
if (input.disableSeriesAbsenceResolution && openSeriesFingerprint) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
988
1133
|
/*
|
|
989
1134
|
* Per-series auto-resolve: when the monitor emits a breaching-
|
|
990
1135
|
* series set and this open incident has a fingerprint, resolve
|
|
@@ -35,6 +35,7 @@ import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/Ex
|
|
|
35
35
|
import { TelemetryQuery } from "../../../Types/Telemetry/TelemetryQuery";
|
|
36
36
|
import MonitorIncident from "./MonitorIncident";
|
|
37
37
|
import MonitorAlert from "./MonitorAlert";
|
|
38
|
+
import IncomingRequestIncidentGrouping from "./IncomingRequestIncidentGrouping";
|
|
38
39
|
import MonitorMaintenanceSuppression from "./MonitorMaintenanceSuppression";
|
|
39
40
|
import MonitorStatusTimelineUtil from "./MonitorStatusTimeline";
|
|
40
41
|
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
@@ -659,6 +660,49 @@ export default class MonitorResourceUtil {
|
|
|
659
660
|
}
|
|
660
661
|
}
|
|
661
662
|
|
|
663
|
+
/*
|
|
664
|
+
* Incoming Request / webhook grouped resolution (event-driven). A
|
|
665
|
+
* payload can explicitly mark keys as resolved (e.g. Grafana
|
|
666
|
+
* status=resolved). Resolve exactly those keys' incidents — never by
|
|
667
|
+
* absence. Runs regardless of whether a firing criteria matched, so a
|
|
668
|
+
* pure "resolved" webhook still closes the right incident. No-op
|
|
669
|
+
* unless a criteria has incidentGrouping configured.
|
|
670
|
+
*/
|
|
671
|
+
if (
|
|
672
|
+
monitor.monitorType === MonitorType.IncomingRequest &&
|
|
673
|
+
criteriaInstances.some((criteriaInstance: MonitorCriteriaInstance) => {
|
|
674
|
+
return IncomingRequestIncidentGrouping.isGroupingConfigured(
|
|
675
|
+
criteriaInstance,
|
|
676
|
+
);
|
|
677
|
+
})
|
|
678
|
+
) {
|
|
679
|
+
const resolvedFingerprints: Array<string> =
|
|
680
|
+
IncomingRequestIncidentGrouping.collectResolvedFingerprints({
|
|
681
|
+
dataToProcess: dataToProcess,
|
|
682
|
+
criteriaInstances: criteriaInstances,
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
if (resolvedFingerprints.length > 0) {
|
|
686
|
+
await MonitorIncident.resolveSeriesIncidentsByFingerprint({
|
|
687
|
+
monitor: monitor,
|
|
688
|
+
fingerprints: resolvedFingerprints,
|
|
689
|
+
rootCause: "Incoming request reported this key as resolved.",
|
|
690
|
+
dataToProcess: dataToProcess,
|
|
691
|
+
autoResolveCriteriaInstanceIdIncidentIdsDictionary,
|
|
692
|
+
evaluationSummary: evaluationSummary,
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
await MonitorAlert.resolveSeriesAlertsByFingerprint({
|
|
696
|
+
monitor: monitor,
|
|
697
|
+
fingerprints: resolvedFingerprints,
|
|
698
|
+
rootCause: "Incoming request reported this key as resolved.",
|
|
699
|
+
dataToProcess: dataToProcess,
|
|
700
|
+
autoResolveCriteriaInstanceIdAlertIdsDictionary,
|
|
701
|
+
evaluationSummary: evaluationSummary,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
662
706
|
if (response.criteriaMetId && response.rootCause) {
|
|
663
707
|
logger.debug(
|
|
664
708
|
`${dataToProcess.monitorId.toString()} - Criteria met: ${
|
|
@@ -796,6 +840,15 @@ export default class MonitorResourceUtil {
|
|
|
796
840
|
},
|
|
797
841
|
matchesPerSeries: response.perSeriesMatches,
|
|
798
842
|
suppressedSeriesFingerprints,
|
|
843
|
+
/*
|
|
844
|
+
* Incoming-request grouping is event-driven: a webhook describes
|
|
845
|
+
* only the keys in its payload, so absence from this tick is not
|
|
846
|
+
* recovery. Skip the snapshot absence-resolve pass; grouped
|
|
847
|
+
* incidents are resolved explicitly via the resolution block
|
|
848
|
+
* above. Per-key create + dedupe still run from matchesPerSeries.
|
|
849
|
+
*/
|
|
850
|
+
disableSeriesAbsenceResolution:
|
|
851
|
+
monitor.monitorType === MonitorType.IncomingRequest,
|
|
799
852
|
});
|
|
800
853
|
|
|
801
854
|
await MonitorAlert.criteriaMetCreateAlertsAndUpdateMonitorStatus({
|
|
@@ -810,6 +863,14 @@ export default class MonitorResourceUtil {
|
|
|
810
863
|
},
|
|
811
864
|
matchesPerSeries: response.perSeriesMatches,
|
|
812
865
|
suppressedSeriesFingerprints,
|
|
866
|
+
/*
|
|
867
|
+
* Incoming-request grouping is event-driven (see the incident
|
|
868
|
+
* create call above): skip the snapshot absence-resolve pass for
|
|
869
|
+
* webhooks; grouped alerts are resolved explicitly via the
|
|
870
|
+
* resolution block above. Per-key create + dedupe still run.
|
|
871
|
+
*/
|
|
872
|
+
disableSeriesAbsenceResolution:
|
|
873
|
+
monitor.monitorType === MonitorType.IncomingRequest,
|
|
813
874
|
});
|
|
814
875
|
} else if (
|
|
815
876
|
!response.criteriaMetId &&
|
|
@@ -828,6 +889,17 @@ export default class MonitorResourceUtil {
|
|
|
828
889
|
criteriaInstance: null, // no criteria met!
|
|
829
890
|
dataToProcess: dataToProcess,
|
|
830
891
|
evaluationSummary: evaluationSummary,
|
|
892
|
+
/*
|
|
893
|
+
* Event-driven grouping: for incoming-request monitors, never
|
|
894
|
+
* absence-resolve per-key (seriesFingerprint) incidents on the
|
|
895
|
+
* no-criteria-met path — a heartbeat tick or a rejected webhook
|
|
896
|
+
* must not bulk-close grouped incidents. They resolve only via
|
|
897
|
+
* the explicit resolution block above. Non-grouped incoming
|
|
898
|
+
* incidents have no seriesFingerprint, so they still resolve
|
|
899
|
+
* normally; non-incoming-request types are unaffected (flag false).
|
|
900
|
+
*/
|
|
901
|
+
disableSeriesAbsenceResolution:
|
|
902
|
+
monitor.monitorType === MonitorType.IncomingRequest,
|
|
831
903
|
});
|
|
832
904
|
|
|
833
905
|
await MonitorAlert.checkOpenAlertsAndCloseIfResolved({
|
|
@@ -837,6 +909,8 @@ export default class MonitorResourceUtil {
|
|
|
837
909
|
criteriaInstance: null, // no criteria met!
|
|
838
910
|
dataToProcess: dataToProcess,
|
|
839
911
|
evaluationSummary: evaluationSummary,
|
|
912
|
+
disableSeriesAbsenceResolution:
|
|
913
|
+
monitor.monitorType === MonitorType.IncomingRequest,
|
|
840
914
|
});
|
|
841
915
|
|
|
842
916
|
// get last monitor status timeline.
|
|
@@ -256,7 +256,8 @@ describe("ClickHouse cluster-aware schema (always-on)", () => {
|
|
|
256
256
|
expect(dropColumn).toContain("SpanItemV3Local");
|
|
257
257
|
expect(dropColumn).toContain("ON CLUSTER 'oneuptime'");
|
|
258
258
|
|
|
259
|
-
const dropIndex: string =
|
|
259
|
+
const dropIndex: string =
|
|
260
|
+
spanGen.toDropSkipIndexStatement("idx_trace_id");
|
|
260
261
|
expect(dropIndex).toContain("SpanItemV3Local");
|
|
261
262
|
expect(dropIndex).toContain("ON CLUSTER 'oneuptime'");
|
|
262
263
|
});
|
|
@@ -19,6 +19,7 @@ import IsNull from "../../../../Types/BaseDatabase/IsNull";
|
|
|
19
19
|
import NotNull from "../../../../Types/BaseDatabase/NotNull";
|
|
20
20
|
import GreaterThan from "../../../../Types/BaseDatabase/GreaterThan";
|
|
21
21
|
import Includes from "../../../../Types/BaseDatabase/Includes";
|
|
22
|
+
import IncludesNone from "../../../../Types/BaseDatabase/IncludesNone";
|
|
22
23
|
import Search from "../../../../Types/BaseDatabase/Search";
|
|
23
24
|
import StartsWith from "../../../../Types/BaseDatabase/StartsWith";
|
|
24
25
|
|
|
@@ -363,6 +364,37 @@ describe("StatementGenerator", () => {
|
|
|
363
364
|
expect(statement.query).toBe("");
|
|
364
365
|
expect(statement.query_params).toStrictEqual({});
|
|
365
366
|
});
|
|
367
|
+
|
|
368
|
+
test("emits direct map subscript NOT IN(...) for IncludesNone wrapper", () => {
|
|
369
|
+
const statement: Statement = mapGenerator.toWhereStatement({
|
|
370
|
+
attributes: {
|
|
371
|
+
"k8s.cluster.name": new IncludesNone(["prod-east", "prod-west"]),
|
|
372
|
+
},
|
|
373
|
+
} as any);
|
|
374
|
+
/*
|
|
375
|
+
* "is none of" emits the same O(1) Map subscript fast path as
|
|
376
|
+
* IN, negated. Missing keys (subscript returns '') pass NOT IN,
|
|
377
|
+
* matching NotEqual's map semantics.
|
|
378
|
+
*/
|
|
379
|
+
expect(statement.query).toBe(
|
|
380
|
+
"AND {p0:Identifier}[{p1:String}] NOT IN {p2:Array(String)}",
|
|
381
|
+
);
|
|
382
|
+
expect(statement.query_params).toStrictEqual({
|
|
383
|
+
p0: "attributes",
|
|
384
|
+
p1: "k8s.cluster.name",
|
|
385
|
+
p2: ["prod-east", "prod-west"],
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("drops empty IncludesNone wrapper instead of producing NOT IN ()", () => {
|
|
390
|
+
const statement: Statement = mapGenerator.toWhereStatement({
|
|
391
|
+
attributes: {
|
|
392
|
+
"k8s.cluster.name": new IncludesNone([]),
|
|
393
|
+
},
|
|
394
|
+
} as any);
|
|
395
|
+
expect(statement.query).toBe("");
|
|
396
|
+
expect(statement.query_params).toStrictEqual({});
|
|
397
|
+
});
|
|
366
398
|
});
|
|
367
399
|
|
|
368
400
|
describe("ArrayText columns", () => {
|