@oneuptime/common 11.5.5 → 11.5.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/Models/DatabaseModels/User.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784048917994-AddCreatedByUserToUser.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +37 -0
- package/Server/Middleware/ProjectAuthorization.ts +40 -15
- package/Server/Services/TeamMemberService.ts +2 -0
- package/Server/Services/UserService.ts +10 -0
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +16 -6
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +14 -0
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +14 -3
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +130 -12
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +553 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +590 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageFormatterUnits.test.ts +254 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +821 -0
- package/build/dist/Models/DatabaseModels/User.js +46 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784048917994-AddCreatedByUserToUser.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784048917994-AddCreatedByUserToUser.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +26 -0
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +40 -14
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +2 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +8 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +15 -7
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +8 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +96 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import MonitorCriteriaMessageBuilder from "../../../../Server/Utils/Monitor/MonitorCriteriaMessageBuilder";
|
|
2
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
3
|
+
import {
|
|
4
|
+
CheckOn,
|
|
5
|
+
CriteriaFilter,
|
|
6
|
+
EvaluateOverTimeType,
|
|
7
|
+
FilterType,
|
|
8
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
9
|
+
import AggregateModel from "../../../../Types/BaseDatabase/AggregatedModel";
|
|
10
|
+
import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
|
|
11
|
+
import MetricAliasData from "../../../../Types/Metrics/MetricAliasData";
|
|
12
|
+
import MetricQueryConfigData from "../../../../Types/Metrics/MetricQueryConfigData";
|
|
13
|
+
import MetricQueryData from "../../../../Types/Metrics/MetricQueryData";
|
|
14
|
+
import MetricsViewConfig from "../../../../Types/Metrics/MetricsViewConfig";
|
|
15
|
+
import MetricMonitorResponse from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
16
|
+
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
17
|
+
import RollingTime from "../../../../Types/RollingTime/RollingTime";
|
|
18
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Assembles the monitor + metric response + step trio the message builder
|
|
22
|
+
* needs, for a single metric alias "a" with a given native/legend unit and
|
|
23
|
+
* sample values. Mirrors the fixture used by MetricMonitorCriteria.test.ts.
|
|
24
|
+
*/
|
|
25
|
+
function buildInputs(input: {
|
|
26
|
+
metricNativeUnit: string;
|
|
27
|
+
metricName: string;
|
|
28
|
+
sampleValues: Array<number>;
|
|
29
|
+
criteriaFilter: CriteriaFilter;
|
|
30
|
+
}): {
|
|
31
|
+
monitor: Monitor;
|
|
32
|
+
criteriaFilter: CriteriaFilter;
|
|
33
|
+
monitorStep: MonitorStep;
|
|
34
|
+
dataToProcess: MetricMonitorResponse;
|
|
35
|
+
} {
|
|
36
|
+
const aliasData: MetricAliasData = {
|
|
37
|
+
metricVariable: "a",
|
|
38
|
+
title: "Response Time",
|
|
39
|
+
description: undefined,
|
|
40
|
+
legend: undefined,
|
|
41
|
+
legendUnit: input.metricNativeUnit,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const queryConfig: MetricQueryConfigData = {
|
|
45
|
+
metricAliasData: aliasData,
|
|
46
|
+
metricQueryData: {
|
|
47
|
+
filterData: {
|
|
48
|
+
metricName: input.metricName,
|
|
49
|
+
},
|
|
50
|
+
} as unknown as MetricQueryData,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const metricViewConfig: MetricsViewConfig = {
|
|
54
|
+
queryConfigs: [queryConfig],
|
|
55
|
+
formulaConfigs: [],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const monitorStep: MonitorStep = new MonitorStep();
|
|
59
|
+
monitorStep.data = {
|
|
60
|
+
id: ObjectID.generate().toString(),
|
|
61
|
+
monitorCriteria: { data: undefined } as never,
|
|
62
|
+
} as unknown as MonitorStep["data"];
|
|
63
|
+
monitorStep.data!.metricMonitor = {
|
|
64
|
+
metricViewConfig,
|
|
65
|
+
rollingTime: RollingTime.Past1Minute,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const aggregated: AggregatedResult = {
|
|
69
|
+
data: input.sampleValues.map((v: number) => {
|
|
70
|
+
return {
|
|
71
|
+
timestamp: new Date(),
|
|
72
|
+
value: v,
|
|
73
|
+
} as AggregateModel;
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const dataToProcess: MetricMonitorResponse = {
|
|
78
|
+
projectId: ObjectID.generate(),
|
|
79
|
+
metricResult: [aggregated],
|
|
80
|
+
metricViewConfig,
|
|
81
|
+
monitorId: ObjectID.generate(),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
monitor: new Monitor(),
|
|
86
|
+
criteriaFilter: input.criteriaFilter,
|
|
87
|
+
monitorStep,
|
|
88
|
+
dataToProcess,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe("MonitorCriteriaMessageBuilder — metric unit labelling", () => {
|
|
93
|
+
test("labels the observed value and threshold with the metric's native unit", () => {
|
|
94
|
+
const criteriaFilter: CriteriaFilter = {
|
|
95
|
+
checkOn: CheckOn.MetricValue,
|
|
96
|
+
filterType: FilterType.GreaterThan,
|
|
97
|
+
value: "5",
|
|
98
|
+
metricMonitorOptions: {
|
|
99
|
+
metricAlias: "a",
|
|
100
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
105
|
+
metricNativeUnit: "sec",
|
|
106
|
+
metricName: "http.client.request.duration",
|
|
107
|
+
sampleValues: [0.06, 0.06, 0.06],
|
|
108
|
+
criteriaFilter,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const message: string =
|
|
112
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
113
|
+
...inputs,
|
|
114
|
+
didMeetCriteria: false,
|
|
115
|
+
matchMessage: null,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Observed value carries the unit.
|
|
119
|
+
expect(message).toContain("latest 0.06 sec");
|
|
120
|
+
expect(message).toContain("min 0.06 sec");
|
|
121
|
+
expect(message).toContain("max 0.06 sec");
|
|
122
|
+
// Threshold in the expectation clause carries the same unit.
|
|
123
|
+
expect(message).toContain("greater than 5 sec");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("uses the user's threshold unit when one is configured", () => {
|
|
127
|
+
const criteriaFilter: CriteriaFilter = {
|
|
128
|
+
checkOn: CheckOn.MetricValue,
|
|
129
|
+
filterType: FilterType.GreaterThan,
|
|
130
|
+
value: "2",
|
|
131
|
+
metricMonitorOptions: {
|
|
132
|
+
metricAlias: "a",
|
|
133
|
+
metricAggregationType: EvaluateOverTimeType.AnyValue,
|
|
134
|
+
thresholdUnit: "sec",
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Samples arrive in ms; converted to sec (0.5) for the message.
|
|
139
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
140
|
+
metricNativeUnit: "ms",
|
|
141
|
+
metricName: "http.client.request.duration",
|
|
142
|
+
sampleValues: [500],
|
|
143
|
+
criteriaFilter,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const message: string =
|
|
147
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
148
|
+
...inputs,
|
|
149
|
+
didMeetCriteria: false,
|
|
150
|
+
matchMessage: null,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(message).toContain("latest 0.50 sec");
|
|
154
|
+
expect(message).toContain("greater than 2 sec");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("omits the unit for dimensionless ratio metrics (native unit '1')", () => {
|
|
158
|
+
const criteriaFilter: CriteriaFilter = {
|
|
159
|
+
checkOn: CheckOn.MetricValue,
|
|
160
|
+
filterType: FilterType.GreaterThan,
|
|
161
|
+
value: "0.9",
|
|
162
|
+
metricMonitorOptions: {
|
|
163
|
+
metricAlias: "a",
|
|
164
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
169
|
+
metricNativeUnit: "1",
|
|
170
|
+
metricName: "system.cpu.utilization",
|
|
171
|
+
sampleValues: [0.06],
|
|
172
|
+
criteriaFilter,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const message: string =
|
|
176
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
177
|
+
...inputs,
|
|
178
|
+
didMeetCriteria: false,
|
|
179
|
+
matchMessage: null,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(message).toContain("latest 0.06");
|
|
183
|
+
// No stray "0.06 1" and no unit on the threshold.
|
|
184
|
+
expect(message).not.toContain("0.06 1");
|
|
185
|
+
expect(message).toContain("greater than 0.9");
|
|
186
|
+
expect(message).not.toContain("greater than 0.9 1");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
/*
|
|
191
|
+
* End-to-end assertions on the whole "Evaluation Logs" line users see:
|
|
192
|
+
* buildCriteriaFilterMessage stitches the observation ("Metric Value (a)
|
|
193
|
+
* recorded latest …") to the expectation ("(expected to be greater than …)").
|
|
194
|
+
* These lock down the exact wording, unit placement, and singular/plural
|
|
195
|
+
* grammar rather than just fragments.
|
|
196
|
+
*/
|
|
197
|
+
describe("MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage — Evaluation Logs line", () => {
|
|
198
|
+
test("native-unit GreaterThan failure renders the exact full message", () => {
|
|
199
|
+
const criteriaFilter: CriteriaFilter = {
|
|
200
|
+
checkOn: CheckOn.MetricValue,
|
|
201
|
+
filterType: FilterType.GreaterThan,
|
|
202
|
+
value: "5",
|
|
203
|
+
metricMonitorOptions: {
|
|
204
|
+
metricAlias: "a",
|
|
205
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
210
|
+
metricNativeUnit: "sec",
|
|
211
|
+
metricName: "http.client.request.duration",
|
|
212
|
+
sampleValues: [0.06, 0.06, 0.06],
|
|
213
|
+
criteriaFilter,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const message: string =
|
|
217
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
218
|
+
...inputs,
|
|
219
|
+
didMeetCriteria: false,
|
|
220
|
+
matchMessage: null,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(message).toBe(
|
|
224
|
+
"Metric Value (a) recorded latest 0.06 sec (min 0.06 sec, max 0.06 sec) across 3 data points. (expected to be greater than 5 sec using average).",
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("LessThan failure carries the unit on both the observed value and the threshold", () => {
|
|
229
|
+
const criteriaFilter: CriteriaFilter = {
|
|
230
|
+
checkOn: CheckOn.MetricValue,
|
|
231
|
+
filterType: FilterType.LessThan,
|
|
232
|
+
value: "5",
|
|
233
|
+
metricMonitorOptions: {
|
|
234
|
+
metricAlias: "a",
|
|
235
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
240
|
+
metricNativeUnit: "sec",
|
|
241
|
+
metricName: "http.client.request.duration",
|
|
242
|
+
sampleValues: [12, 8, 10],
|
|
243
|
+
criteriaFilter,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const message: string =
|
|
247
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
248
|
+
...inputs,
|
|
249
|
+
didMeetCriteria: false,
|
|
250
|
+
matchMessage: null,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(message).toBe(
|
|
254
|
+
"Metric Value (a) recorded latest 10.00 sec (min 8.00 sec, max 12.00 sec) across 3 data points. (expected to be less than 5 sec using average).",
|
|
255
|
+
);
|
|
256
|
+
// The unit rides both the observed value and the "less than N unit" clause.
|
|
257
|
+
expect(message).toContain("latest 10.00 sec");
|
|
258
|
+
expect(message).toContain("less than 5 sec");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("thresholdUnit conversion: native ms samples display as sec and the threshold reads 'sec'", () => {
|
|
262
|
+
const criteriaFilter: CriteriaFilter = {
|
|
263
|
+
checkOn: CheckOn.MetricValue,
|
|
264
|
+
filterType: FilterType.GreaterThan,
|
|
265
|
+
value: "2",
|
|
266
|
+
metricMonitorOptions: {
|
|
267
|
+
metricAlias: "a",
|
|
268
|
+
metricAggregationType: EvaluateOverTimeType.MaximumValue,
|
|
269
|
+
thresholdUnit: "sec",
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// 1500/2500/3500 ms convert to 1.5/2.5/3.5 sec for display.
|
|
274
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
275
|
+
metricNativeUnit: "ms",
|
|
276
|
+
metricName: "http.client.request.duration",
|
|
277
|
+
sampleValues: [1500, 2500, 3500],
|
|
278
|
+
criteriaFilter,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const message: string =
|
|
282
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
283
|
+
...inputs,
|
|
284
|
+
didMeetCriteria: false,
|
|
285
|
+
matchMessage: null,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(message).toBe(
|
|
289
|
+
"Metric Value (a) recorded latest 3.50 sec (min 1.50 sec, max 3.50 sec) across 3 data points. (expected to be greater than 2 sec using maximum value).",
|
|
290
|
+
);
|
|
291
|
+
// The raw ms magnitudes never leak into the rendered line.
|
|
292
|
+
expect(message).not.toContain("1500");
|
|
293
|
+
expect(message).not.toContain(" ms");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("dimensionless ratio metric (unit '1') labels neither the value nor the threshold", () => {
|
|
297
|
+
const criteriaFilter: CriteriaFilter = {
|
|
298
|
+
checkOn: CheckOn.MetricValue,
|
|
299
|
+
filterType: FilterType.GreaterThan,
|
|
300
|
+
value: "0.9",
|
|
301
|
+
metricMonitorOptions: {
|
|
302
|
+
metricAlias: "a",
|
|
303
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/*
|
|
308
|
+
* A ratio pegged at 1.0 (100%) renders "1.00" — the exact case a naive
|
|
309
|
+
* `not.toContain(" 1")` guard would false-fail on, since " 1.00" contains
|
|
310
|
+
* " 1". We assert the whole message with toBe instead, which stays honest
|
|
311
|
+
* whether or not a rendered number happens to start with a 1.
|
|
312
|
+
*/
|
|
313
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
314
|
+
metricNativeUnit: "1",
|
|
315
|
+
metricName: "system.cpu.utilization",
|
|
316
|
+
sampleValues: [0.95, 0.99, 1.0],
|
|
317
|
+
criteriaFilter,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const message: string =
|
|
321
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
322
|
+
...inputs,
|
|
323
|
+
didMeetCriteria: false,
|
|
324
|
+
matchMessage: null,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
/*
|
|
328
|
+
* Whole-message assertion: the dimensionless "1" is suppressed on both the
|
|
329
|
+
* observed value and the threshold, even though "1.00" appears in the text.
|
|
330
|
+
*/
|
|
331
|
+
expect(message).toBe(
|
|
332
|
+
"Metric Value (a) recorded latest 1.00 (min 0.95, max 1.00) across 3 data points. (expected to be greater than 0.9 using average).",
|
|
333
|
+
);
|
|
334
|
+
/*
|
|
335
|
+
* Neither the value nor the threshold carries a unit: each number is
|
|
336
|
+
* immediately followed by punctuation/wording, never a " 1" unit suffix.
|
|
337
|
+
*/
|
|
338
|
+
expect(message).toContain("latest 1.00 (min 0.95, max 1.00)");
|
|
339
|
+
expect(message).toContain("greater than 0.9 using average");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
/*
|
|
343
|
+
* Flagship fraction→percent end-to-end: a ratio metric whose native unit is
|
|
344
|
+
* the dimensionless "1" is compared against a "%" threshold. The lone 0.06
|
|
345
|
+
* sample is a 6% fraction; it is converted INTO "%" for display (0.06 * 100
|
|
346
|
+
* = 6 → "6.00 %"), and BOTH the observed value and the threshold are
|
|
347
|
+
* labelled "%". The single sample takes the singular "across 1 data point"
|
|
348
|
+
* with no "(min .., max ..)" clause.
|
|
349
|
+
*/
|
|
350
|
+
test("fraction native unit converts into a '%' threshold on both the value and the threshold", () => {
|
|
351
|
+
const criteriaFilter: CriteriaFilter = {
|
|
352
|
+
checkOn: CheckOn.MetricValue,
|
|
353
|
+
filterType: FilterType.GreaterThan,
|
|
354
|
+
value: "90",
|
|
355
|
+
metricMonitorOptions: {
|
|
356
|
+
metricAlias: "a",
|
|
357
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
358
|
+
thresholdUnit: "%",
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// Native "1" sample 0.06 (a 6% fraction) converts to 6 in "%".
|
|
363
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
364
|
+
metricNativeUnit: "1",
|
|
365
|
+
metricName: "system.cpu.utilization",
|
|
366
|
+
sampleValues: [0.06],
|
|
367
|
+
criteriaFilter,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const message: string =
|
|
371
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
372
|
+
...inputs,
|
|
373
|
+
didMeetCriteria: false,
|
|
374
|
+
matchMessage: null,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
expect(message).toBe(
|
|
378
|
+
"Metric Value (a) recorded latest 6.00 % across 1 data point. (expected to be greater than 90 % using average).",
|
|
379
|
+
);
|
|
380
|
+
// Both the converted observed value and the threshold carry the "%" unit.
|
|
381
|
+
expect(message).toContain("latest 6.00 %");
|
|
382
|
+
expect(message).toContain("greater than 90 %");
|
|
383
|
+
// The raw fraction magnitude never leaks into the rendered line.
|
|
384
|
+
expect(message).not.toContain("0.06");
|
|
385
|
+
// Singular grammar for the lone sample — no "(min .., max ..)" clause.
|
|
386
|
+
expect(message).toContain("across 1 data point.");
|
|
387
|
+
expect(message).not.toContain("data points");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("matchMessage is returned verbatim, ignoring observation and expectation", () => {
|
|
391
|
+
const criteriaFilter: CriteriaFilter = {
|
|
392
|
+
checkOn: CheckOn.MetricValue,
|
|
393
|
+
filterType: FilterType.GreaterThan,
|
|
394
|
+
value: "5",
|
|
395
|
+
metricMonitorOptions: {
|
|
396
|
+
metricAlias: "a",
|
|
397
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
402
|
+
metricNativeUnit: "sec",
|
|
403
|
+
metricName: "http.client.request.duration",
|
|
404
|
+
sampleValues: [0.06, 0.06, 0.06],
|
|
405
|
+
criteriaFilter,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const matchMessage: string =
|
|
409
|
+
"CPU on prod-01 climbed to 0.95 across the last 3 samples.";
|
|
410
|
+
|
|
411
|
+
// Passthrough wins over a failing evaluation …
|
|
412
|
+
expect(
|
|
413
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
414
|
+
...inputs,
|
|
415
|
+
didMeetCriteria: false,
|
|
416
|
+
matchMessage,
|
|
417
|
+
}),
|
|
418
|
+
).toBe(matchMessage);
|
|
419
|
+
|
|
420
|
+
// … and even over a met criteria (matchMessage is checked first).
|
|
421
|
+
const passthroughWhenMet: string =
|
|
422
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
423
|
+
...inputs,
|
|
424
|
+
didMeetCriteria: true,
|
|
425
|
+
matchMessage,
|
|
426
|
+
});
|
|
427
|
+
expect(passthroughWhenMet).toBe(matchMessage);
|
|
428
|
+
// None of the synthesized wording bleeds through.
|
|
429
|
+
expect(passthroughWhenMet).not.toContain("recorded");
|
|
430
|
+
expect(passthroughWhenMet).not.toContain("expected");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("didMeetCriteria true yields a 'condition met.' line with no observed numbers", () => {
|
|
434
|
+
const criteriaFilter: CriteriaFilter = {
|
|
435
|
+
checkOn: CheckOn.MetricValue,
|
|
436
|
+
filterType: FilterType.GreaterThan,
|
|
437
|
+
value: "5",
|
|
438
|
+
metricMonitorOptions: {
|
|
439
|
+
metricAlias: "a",
|
|
440
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
445
|
+
metricNativeUnit: "sec",
|
|
446
|
+
metricName: "http.client.request.duration",
|
|
447
|
+
sampleValues: [0.06, 0.06, 0.06],
|
|
448
|
+
criteriaFilter,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const message: string =
|
|
452
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
453
|
+
...inputs,
|
|
454
|
+
didMeetCriteria: true,
|
|
455
|
+
matchMessage: null,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
expect(message).toBe("Metric Value Greater Than 5 condition met.");
|
|
459
|
+
// The met-condition line summarizes intent, not the raw samples.
|
|
460
|
+
expect(message).not.toContain("recorded");
|
|
461
|
+
expect(message).not.toContain("0.06");
|
|
462
|
+
expect(message).not.toContain("data point");
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("single-sample failure uses the singular 'across 1 data point' with a unit", () => {
|
|
466
|
+
const criteriaFilter: CriteriaFilter = {
|
|
467
|
+
checkOn: CheckOn.MetricValue,
|
|
468
|
+
filterType: FilterType.GreaterThan,
|
|
469
|
+
value: "5",
|
|
470
|
+
metricMonitorOptions: {
|
|
471
|
+
metricAlias: "a",
|
|
472
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
477
|
+
metricNativeUnit: "sec",
|
|
478
|
+
metricName: "http.client.request.duration",
|
|
479
|
+
sampleValues: [7],
|
|
480
|
+
criteriaFilter,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const message: string =
|
|
484
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
485
|
+
...inputs,
|
|
486
|
+
didMeetCriteria: false,
|
|
487
|
+
matchMessage: null,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
expect(message).toBe(
|
|
491
|
+
"Metric Value (a) recorded latest 7.00 sec across 1 data point. (expected to be greater than 5 sec using average).",
|
|
492
|
+
);
|
|
493
|
+
// Singular grammar, and no "(min .., max ..)" clause for a lone sample.
|
|
494
|
+
expect(message).toContain("across 1 data point.");
|
|
495
|
+
expect(message).not.toContain("data points");
|
|
496
|
+
expect(message).not.toContain("min 7.00");
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("EqualTo scenario renders the full line with the metric unit", () => {
|
|
500
|
+
const criteriaFilter: CriteriaFilter = {
|
|
501
|
+
checkOn: CheckOn.MetricValue,
|
|
502
|
+
filterType: FilterType.EqualTo,
|
|
503
|
+
value: "100",
|
|
504
|
+
metricMonitorOptions: {
|
|
505
|
+
metricAlias: "a",
|
|
506
|
+
metricAggregationType: EvaluateOverTimeType.Average,
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
511
|
+
metricNativeUnit: "ms",
|
|
512
|
+
metricName: "http.client.request.duration",
|
|
513
|
+
sampleValues: [100, 100, 100],
|
|
514
|
+
criteriaFilter,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const message: string =
|
|
518
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
519
|
+
...inputs,
|
|
520
|
+
didMeetCriteria: false,
|
|
521
|
+
matchMessage: null,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
expect(message).toBe(
|
|
525
|
+
"Metric Value (a) recorded latest 100.00 ms (min 100.00 ms, max 100.00 ms) across 3 data points. (expected to equal 100 ms using average).",
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test("GreaterThanOrEqualTo scenario renders the full line with the metric unit", () => {
|
|
530
|
+
const criteriaFilter: CriteriaFilter = {
|
|
531
|
+
checkOn: CheckOn.MetricValue,
|
|
532
|
+
filterType: FilterType.GreaterThanOrEqualTo,
|
|
533
|
+
value: "50",
|
|
534
|
+
metricMonitorOptions: {
|
|
535
|
+
metricAlias: "a",
|
|
536
|
+
metricAggregationType: EvaluateOverTimeType.MaximumValue,
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
541
|
+
metricNativeUnit: "ms",
|
|
542
|
+
metricName: "http.client.request.duration",
|
|
543
|
+
sampleValues: [60, 55, 70],
|
|
544
|
+
criteriaFilter,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const message: string =
|
|
548
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
549
|
+
...inputs,
|
|
550
|
+
didMeetCriteria: false,
|
|
551
|
+
matchMessage: null,
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
expect(message).toBe(
|
|
555
|
+
"Metric Value (a) recorded latest 70.00 ms (min 55.00 ms, max 70.00 ms) across 3 data points. (expected to be greater than or equal to 50 ms using maximum value).",
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("the parenthesized expectation follows a period + space after the observation", () => {
|
|
560
|
+
const criteriaFilter: CriteriaFilter = {
|
|
561
|
+
checkOn: CheckOn.MetricValue,
|
|
562
|
+
filterType: FilterType.GreaterThan,
|
|
563
|
+
value: "5",
|
|
564
|
+
metricMonitorOptions: {
|
|
565
|
+
metricAlias: "a",
|
|
566
|
+
metricAggregationType: EvaluateOverTimeType.AnyValue,
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
571
|
+
metricNativeUnit: "sec",
|
|
572
|
+
metricName: "http.client.request.duration",
|
|
573
|
+
sampleValues: [0.06, 0.06, 0.06],
|
|
574
|
+
criteriaFilter,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const message: string =
|
|
578
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
579
|
+
...inputs,
|
|
580
|
+
didMeetCriteria: false,
|
|
581
|
+
matchMessage: null,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// "... data points. (expected ...)." — period, space, then the clause.
|
|
585
|
+
expect(message).toContain("data points. (expected ");
|
|
586
|
+
expect(message).toMatch(/ data points\. \(expected .+\)\.$/);
|
|
587
|
+
// AnyValue lowercases to "any value".
|
|
588
|
+
expect(message).toContain("using any value");
|
|
589
|
+
});
|
|
590
|
+
});
|