@oneuptime/common 11.5.13 → 11.6.1

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 (156) hide show
  1. package/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +41 -24
  2. package/Models/AnalyticsModels/MetricBaselineHourly.ts +2 -0
  3. package/Models/AnalyticsModels/MetricItemAggMV1m.ts +5 -2
  4. package/Models/AnalyticsModels/MetricItemAggMV1mByContainer.ts +5 -2
  5. package/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.ts +5 -2
  6. package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +5 -2
  7. package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +5 -2
  8. package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
  9. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
  10. package/Models/DatabaseModels/GlobalConfig.ts +76 -0
  11. package/Server/API/EnterpriseLicenseAPI.ts +57 -0
  12. package/Server/API/GlobalConfigAPI.ts +62 -1
  13. package/Server/EnvironmentConfig.ts +22 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  17. package/Server/Services/AnalyticsDatabaseService.ts +61 -46
  18. package/Server/Services/DatabaseService.ts +66 -1
  19. package/Server/Services/WorkflowService.ts +100 -39
  20. package/Server/Types/Workflow/Components/Schedule.ts +57 -32
  21. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
  22. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +41 -4
  23. package/Server/Utils/Express.ts +13 -0
  24. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
  25. package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
  26. package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
  27. package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
  28. package/Tests/Models/AnalyticsModels/AnalyticsBaseModel.test.ts +122 -0
  29. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
  30. package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
  31. package/Tests/Server/Services/WorkflowService.test.ts +308 -0
  32. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +164 -3
  33. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +99 -0
  34. package/Tests/Server/Utils/Attribution.test.ts +203 -0
  35. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
  36. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
  37. package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
  38. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
  39. package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
  40. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
  41. package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
  42. package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
  43. package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
  44. package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
  45. package/Tests/Types/HashCode.test.ts +41 -0
  46. package/Tests/Types/Log/LogQueryParser.test.ts +186 -0
  47. package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
  48. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
  49. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
  50. package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
  51. package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
  52. package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
  53. package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
  54. package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
  55. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
  56. package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
  57. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
  58. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
  59. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
  60. package/Tests/Utils/Array.test.ts +156 -0
  61. package/Tests/Utils/CronTab.test.ts +199 -0
  62. package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +197 -0
  63. package/Tests/Utils/ModelImportExport.test.ts +101 -0
  64. package/Tests/Utils/Number.test.ts +179 -0
  65. package/Tests/Utils/ValueFormatter.test.ts +305 -0
  66. package/Tests/Utils/VersionUtil.test.ts +348 -0
  67. package/Tests/jest.setup.ts +1 -0
  68. package/Types/AnalyticsDatabase/TableColumn.ts +23 -0
  69. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
  70. package/Types/Monitor/MonitorStep.ts +24 -4
  71. package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
  72. package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
  73. package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
  74. package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
  75. package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
  76. package/UI/Utils/NotificationMethodUtil.ts +310 -0
  77. package/Utils/CronTab.ts +823 -0
  78. package/Utils/ModelImportExport.ts +21 -6
  79. package/Utils/VersionUtil.ts +260 -0
  80. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +25 -20
  81. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
  82. package/build/dist/Models/AnalyticsModels/MetricBaselineHourly.js +2 -0
  83. package/build/dist/Models/AnalyticsModels/MetricBaselineHourly.js.map +1 -1
  84. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1m.js +5 -2
  85. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1m.js.map +1 -1
  86. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js +5 -2
  87. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js.map +1 -1
  88. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.js +5 -2
  89. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.js.map +1 -1
  90. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +5 -2
  91. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js.map +1 -1
  92. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +5 -2
  93. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
  95. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
  97. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
  99. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  100. package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
  101. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  102. package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
  103. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  104. package/build/dist/Server/EnvironmentConfig.js +17 -0
  105. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  112. package/build/dist/Server/Services/AnalyticsDatabaseService.js +53 -17
  113. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  114. package/build/dist/Server/Services/DatabaseService.js +53 -1
  115. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  116. package/build/dist/Server/Services/WorkflowService.js +80 -30
  117. package/build/dist/Server/Services/WorkflowService.js.map +1 -1
  118. package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
  119. package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
  120. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
  121. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
  122. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +29 -4
  123. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  124. package/build/dist/Server/Utils/Express.js +12 -0
  125. package/build/dist/Server/Utils/Express.js.map +1 -1
  126. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
  127. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
  128. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
  129. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
  130. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
  131. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
  132. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
  133. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
  134. package/build/dist/Types/AnalyticsDatabase/TableColumn.js +7 -0
  135. package/build/dist/Types/AnalyticsDatabase/TableColumn.js.map +1 -1
  136. package/build/dist/Types/Monitor/MonitorStep.js +20 -4
  137. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  138. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
  139. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
  140. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
  141. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  142. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
  143. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  144. package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
  145. package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
  146. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
  147. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
  148. package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
  149. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
  150. package/build/dist/Utils/CronTab.js +609 -0
  151. package/build/dist/Utils/CronTab.js.map +1 -0
  152. package/build/dist/Utils/ModelImportExport.js +20 -6
  153. package/build/dist/Utils/ModelImportExport.js.map +1 -1
  154. package/build/dist/Utils/VersionUtil.js +193 -0
  155. package/build/dist/Utils/VersionUtil.js.map +1 -0
  156. package/package.json +1 -1
@@ -0,0 +1,245 @@
1
+ import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
2
+ import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
3
+ import MetricsViewConfig from "../../../Types/Metrics/MetricsViewConfig";
4
+ import { MonitorStepType } from "../../../Types/Monitor/MonitorStep";
5
+ import MonitorStepMetricViewConfigUtil from "../../../Types/Monitor/MonitorStepMetricViewConfigUtil";
6
+
7
+ /*
8
+ * Regression coverage for the "No options" metric dropdown bug.
9
+ *
10
+ * The alert-criteria "Which metric query should this alert rule check?"
11
+ * dropdown is populated from whichever metric-shaped monitor sub-config is
12
+ * present on the step. Before this util existed the resolution was an inline
13
+ * OR-chain that only read metricMonitor / kubernetesMonitor / dockerMonitor /
14
+ * proxmoxMonitor / cephMonitor — so Host, Podman, DockerSwarm and IoT monitors
15
+ * (all of which DO carry a metricViewConfig) surfaced ZERO options even though
16
+ * their step forms had populated real query configs.
17
+ *
18
+ * These tests lock in that EVERY metric-shaped key is resolved, and that the
19
+ * variable-collection semantics (dedup, drop-empty, include formulas) match
20
+ * what the dropdown expects.
21
+ */
22
+
23
+ // Every monitor-step key that carries a metricViewConfig.
24
+ const METRIC_SHAPED_KEYS: Array<keyof MonitorStepType> = [
25
+ "metricMonitor",
26
+ "hostMonitor",
27
+ "kubernetesMonitor",
28
+ "dockerMonitor",
29
+ "dockerSwarmMonitor",
30
+ "podmanMonitor",
31
+ "proxmoxMonitor",
32
+ "cephMonitor",
33
+ "iotMonitor",
34
+ ];
35
+
36
+ function queryWithVariable(
37
+ metricVariable: string,
38
+ groupByAttributeKeys?: Array<string>,
39
+ ): MetricQueryConfigData {
40
+ return {
41
+ metricAliasData: {
42
+ metricVariable,
43
+ title: metricVariable,
44
+ description: metricVariable,
45
+ legend: metricVariable,
46
+ legendUnit: undefined,
47
+ },
48
+ metricQueryData: {
49
+ filterData: {},
50
+ groupByAttributeKeys,
51
+ },
52
+ } as unknown as MetricQueryConfigData;
53
+ }
54
+
55
+ function formulaWithVariable(metricVariable: string): MetricFormulaConfigData {
56
+ return {
57
+ metricAliasData: {
58
+ metricVariable,
59
+ title: metricVariable,
60
+ description: metricVariable,
61
+ legend: metricVariable,
62
+ legendUnit: undefined,
63
+ },
64
+ metricFormulaData: {
65
+ metricFormula: "a + b",
66
+ },
67
+ } as unknown as MetricFormulaConfigData;
68
+ }
69
+
70
+ function viewConfig(
71
+ queryVariables: Array<string>,
72
+ formulaVariables: Array<string> = [],
73
+ ): MetricsViewConfig {
74
+ return {
75
+ queryConfigs: queryVariables.map((v: string) => {
76
+ return queryWithVariable(v);
77
+ }),
78
+ formulaConfigs: formulaVariables.map((v: string) => {
79
+ return formulaWithVariable(v);
80
+ }),
81
+ };
82
+ }
83
+
84
+ function stepDataWith(
85
+ key: keyof MonitorStepType,
86
+ config: MetricsViewConfig,
87
+ ): MonitorStepType {
88
+ return {
89
+ [key]: {
90
+ metricViewConfig: config,
91
+ },
92
+ } as unknown as MonitorStepType;
93
+ }
94
+
95
+ describe("MonitorStepMetricViewConfigUtil", () => {
96
+ describe("getMetricViewConfig", () => {
97
+ test("returns undefined for undefined step data", () => {
98
+ expect(
99
+ MonitorStepMetricViewConfigUtil.getMetricViewConfig(undefined),
100
+ ).toBeUndefined();
101
+ });
102
+
103
+ test("returns undefined for a non-metric-shaped monitor step", () => {
104
+ const stepData: MonitorStepType = {
105
+ logMonitor: { someField: true },
106
+ } as unknown as MonitorStepType;
107
+
108
+ expect(
109
+ MonitorStepMetricViewConfigUtil.getMetricViewConfig(stepData),
110
+ ).toBeUndefined();
111
+ });
112
+
113
+ test("returns undefined when the metric sub-config has no metricViewConfig", () => {
114
+ const stepData: MonitorStepType = {
115
+ hostMonitor: { hostIdentifier: "h-1" },
116
+ } as unknown as MonitorStepType;
117
+
118
+ expect(
119
+ MonitorStepMetricViewConfigUtil.getMetricViewConfig(stepData),
120
+ ).toBeUndefined();
121
+ });
122
+
123
+ // The core of the bug: each metric-shaped key must be resolvable.
124
+ test.each(METRIC_SHAPED_KEYS)(
125
+ "resolves metricViewConfig from %s",
126
+ (key: keyof MonitorStepType) => {
127
+ const config: MetricsViewConfig = viewConfig(["a"]);
128
+ const stepData: MonitorStepType = stepDataWith(key, config);
129
+
130
+ expect(
131
+ MonitorStepMetricViewConfigUtil.getMetricViewConfig(stepData),
132
+ ).toBe(config);
133
+ },
134
+ );
135
+
136
+ test("prefers metricMonitor when multiple metric configs are present", () => {
137
+ const metricConfig: MetricsViewConfig = viewConfig(["metric"]);
138
+ const hostConfig: MetricsViewConfig = viewConfig(["host"]);
139
+ const stepData: MonitorStepType = {
140
+ metricMonitor: { metricViewConfig: metricConfig },
141
+ hostMonitor: { metricViewConfig: hostConfig },
142
+ } as unknown as MonitorStepType;
143
+
144
+ expect(
145
+ MonitorStepMetricViewConfigUtil.getMetricViewConfig(stepData),
146
+ ).toBe(metricConfig);
147
+ });
148
+ });
149
+
150
+ describe("getMetricVariables", () => {
151
+ test("returns [] for undefined step data", () => {
152
+ expect(
153
+ MonitorStepMetricViewConfigUtil.getMetricVariables(undefined),
154
+ ).toEqual([]);
155
+ });
156
+
157
+ test("returns [] for a non-metric-shaped monitor step", () => {
158
+ const stepData: MonitorStepType = {
159
+ dnsMonitor: {},
160
+ } as unknown as MonitorStepType;
161
+
162
+ expect(
163
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
164
+ ).toEqual([]);
165
+ });
166
+
167
+ /*
168
+ * Each metric-shaped key must surface its query variables (this is
169
+ * exactly the option list the criteria "Metric" dropdown renders).
170
+ */
171
+ test.each(METRIC_SHAPED_KEYS)(
172
+ "surfaces query variables from %s",
173
+ (key: keyof MonitorStepType) => {
174
+ const stepData: MonitorStepType = stepDataWith(
175
+ key,
176
+ viewConfig(["cpu", "memory"]),
177
+ );
178
+
179
+ expect(
180
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
181
+ ).toEqual(["cpu", "memory"]);
182
+ },
183
+ );
184
+
185
+ test("includes formula variables after query variables", () => {
186
+ const stepData: MonitorStepType = stepDataWith(
187
+ "hostMonitor",
188
+ viewConfig(["a", "b"], ["c"]),
189
+ );
190
+
191
+ expect(
192
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
193
+ ).toEqual(["a", "b", "c"]);
194
+ });
195
+
196
+ test("removes duplicate variables while preserving first-seen order", () => {
197
+ const stepData: MonitorStepType = stepDataWith(
198
+ "dockerMonitor",
199
+ viewConfig(["a", "a", "b"], ["b", "c"]),
200
+ );
201
+
202
+ expect(
203
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
204
+ ).toEqual(["a", "b", "c"]);
205
+ });
206
+
207
+ test("drops empty and missing variables", () => {
208
+ const stepData: MonitorStepType = stepDataWith(
209
+ "podmanMonitor",
210
+ viewConfig(["a", "", "b"]),
211
+ );
212
+
213
+ expect(
214
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
215
+ ).toEqual(["a", "b"]);
216
+ });
217
+
218
+ test("returns [] when queryConfigs and formulaConfigs are empty", () => {
219
+ const stepData: MonitorStepType = stepDataWith("iotMonitor", {
220
+ queryConfigs: [],
221
+ formulaConfigs: [],
222
+ });
223
+
224
+ expect(
225
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
226
+ ).toEqual([]);
227
+ });
228
+
229
+ /*
230
+ * Custom Metric flow: the alias is the (possibly long) metric id with
231
+ * dashes swapped for underscores, e.g. process_cpu_utilization. The
232
+ * dropdown must still offer it.
233
+ */
234
+ test("surfaces a long descriptive alias from the custom-metric flow", () => {
235
+ const stepData: MonitorStepType = stepDataWith(
236
+ "hostMonitor",
237
+ viewConfig(["process_cpu_utilization"]),
238
+ );
239
+
240
+ expect(
241
+ MonitorStepMetricViewConfigUtil.getMetricVariables(stepData),
242
+ ).toEqual(["process_cpu_utilization"]);
243
+ });
244
+ });
245
+ });
@@ -0,0 +1,155 @@
1
+ import { JSONObject } from "../../../Types/JSON";
2
+ import MonitorStepNetworkDeviceMonitor, {
3
+ MonitorStepNetworkDeviceMonitorUtil,
4
+ } from "../../../Types/Monitor/MonitorStepNetworkDeviceMonitor";
5
+ import SnmpOid from "../../../Types/Monitor/SnmpMonitor/SnmpOid";
6
+
7
+ describe("MonitorStepNetworkDeviceMonitorUtil", () => {
8
+ describe("getDefault", () => {
9
+ test("returns no device, interface monitoring on, and no oids", () => {
10
+ const def: MonitorStepNetworkDeviceMonitor =
11
+ MonitorStepNetworkDeviceMonitorUtil.getDefault();
12
+
13
+ expect(def.networkDeviceId).toBeUndefined();
14
+ expect(def.monitorInterfaces).toBe(true);
15
+ expect(def.oids).toEqual([]);
16
+ });
17
+ });
18
+
19
+ describe("fromJSON", () => {
20
+ test("reads the fields from a full JSON object", () => {
21
+ const monitor: MonitorStepNetworkDeviceMonitor =
22
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON({
23
+ networkDeviceId: "device-1",
24
+ monitorInterfaces: false,
25
+ oids: [
26
+ {
27
+ oid: "1.3.6.1.2.1.1.1.0",
28
+ name: "sysDescr",
29
+ description: "System Description",
30
+ },
31
+ ],
32
+ });
33
+
34
+ expect(monitor.networkDeviceId).toBe("device-1");
35
+ expect(monitor.monitorInterfaces).toBe(false);
36
+ expect(monitor.oids.length).toBe(1);
37
+ expect(monitor.oids[0]!.oid).toBe("1.3.6.1.2.1.1.1.0");
38
+ expect(monitor.oids[0]!.name).toBe("sysDescr");
39
+ expect(monitor.oids[0]!.description).toBe("System Description");
40
+ });
41
+
42
+ test("defaults monitorInterfaces to true unless it is explicitly false", () => {
43
+ // Missing -> true.
44
+ expect(
45
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON({}).monitorInterfaces,
46
+ ).toBe(true);
47
+ // Any non-false value (e.g. a truthy) -> true.
48
+ expect(
49
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON({
50
+ monitorInterfaces: true,
51
+ }).monitorInterfaces,
52
+ ).toBe(true);
53
+ // Explicit false -> false.
54
+ expect(
55
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON({
56
+ monitorInterfaces: false,
57
+ }).monitorInterfaces,
58
+ ).toBe(false);
59
+ });
60
+
61
+ test("coerces a missing networkDeviceId to undefined and missing oids to []", () => {
62
+ const monitor: MonitorStepNetworkDeviceMonitor =
63
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON({});
64
+
65
+ expect(monitor.networkDeviceId).toBeUndefined();
66
+ expect(monitor.oids).toEqual([]);
67
+ });
68
+
69
+ test("fills missing oid sub-fields with sensible defaults", () => {
70
+ const monitor: MonitorStepNetworkDeviceMonitor =
71
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON({
72
+ oids: [{}, { oid: "1.2.3" }],
73
+ });
74
+
75
+ // A row with no oid string becomes an empty oid, name/description undefined.
76
+ expect(monitor.oids[0]!.oid).toBe("");
77
+ expect(monitor.oids[0]!.name).toBeUndefined();
78
+ expect(monitor.oids[0]!.description).toBeUndefined();
79
+ expect(monitor.oids[1]!.oid).toBe("1.2.3");
80
+ });
81
+ });
82
+
83
+ describe("toJSON", () => {
84
+ test("serializes each field including nested oids", () => {
85
+ const monitor: MonitorStepNetworkDeviceMonitor = {
86
+ networkDeviceId: "device-42",
87
+ monitorInterfaces: false,
88
+ oids: [{ oid: "1.2.3", name: "n", description: "d" }],
89
+ };
90
+
91
+ const json: JSONObject =
92
+ MonitorStepNetworkDeviceMonitorUtil.toJSON(monitor);
93
+
94
+ expect(json["networkDeviceId"]).toBe("device-42");
95
+ expect(json["monitorInterfaces"]).toBe(false);
96
+ expect(json["oids"]).toEqual([
97
+ { oid: "1.2.3", name: "n", description: "d" },
98
+ ]);
99
+ });
100
+ });
101
+
102
+ describe("round trip", () => {
103
+ test("fromJSON(toJSON(x)) preserves a fully-populated monitor", () => {
104
+ const original: MonitorStepNetworkDeviceMonitor = {
105
+ networkDeviceId: "device-7",
106
+ monitorInterfaces: false,
107
+ oids: [
108
+ { oid: "1.3.6.1.2.1.1.1.0", name: "sysDescr", description: "desc" },
109
+ {
110
+ oid: "1.3.6.1.2.1.1.3.0",
111
+ name: "sysUpTime",
112
+ description: "uptime",
113
+ },
114
+ ],
115
+ };
116
+
117
+ const roundTripped: MonitorStepNetworkDeviceMonitor =
118
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON(
119
+ MonitorStepNetworkDeviceMonitorUtil.toJSON(original),
120
+ );
121
+
122
+ expect(roundTripped).toEqual(original);
123
+ });
124
+
125
+ test("the default value round-trips unchanged", () => {
126
+ const def: MonitorStepNetworkDeviceMonitor =
127
+ MonitorStepNetworkDeviceMonitorUtil.getDefault();
128
+
129
+ const roundTripped: MonitorStepNetworkDeviceMonitor =
130
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON(
131
+ MonitorStepNetworkDeviceMonitorUtil.toJSON(def),
132
+ );
133
+
134
+ expect(roundTripped).toEqual(def);
135
+ });
136
+
137
+ test("preserves oids with only an oid string across a round trip", () => {
138
+ const original: MonitorStepNetworkDeviceMonitor = {
139
+ networkDeviceId: undefined,
140
+ monitorInterfaces: true,
141
+ oids: [{ oid: "1.2.3", name: undefined, description: undefined }],
142
+ };
143
+
144
+ const roundTripped: MonitorStepNetworkDeviceMonitor =
145
+ MonitorStepNetworkDeviceMonitorUtil.fromJSON(
146
+ MonitorStepNetworkDeviceMonitorUtil.toJSON(original),
147
+ );
148
+
149
+ const oid: SnmpOid = roundTripped.oids[0]!;
150
+ expect(oid.oid).toBe("1.2.3");
151
+ expect(oid.name).toBeUndefined();
152
+ expect(oid.description).toBeUndefined();
153
+ });
154
+ });
155
+ });
@@ -0,0 +1,141 @@
1
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
2
+ import MonitorType from "../../../Types/Monitor/MonitorType";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import LogSeverity from "../../../Types/Log/LogSeverity";
5
+ import MonitorStepLogMonitor from "../../../Types/Monitor/MonitorStepLogMonitor";
6
+ import { JSONObject } from "../../../Types/JSON";
7
+
8
+ /*
9
+ * Guards the persistence half of the "log monitors do not work" fix. The bug
10
+ * hinged on MonitorStep.toJSON() serializing a sub-config only when it is
11
+ * truthy: a step whose logMonitor was undefined serialized to no logMonitor at
12
+ * all, so the config was silently dropped on save and the worker later saw an
13
+ * empty config. These tests prove (a) a seeded telemetry sub-config survives a
14
+ * toJSON -> fromJSON round-trip, (b) user-entered filters survive it, and
15
+ * (c) the drop-when-undefined behavior the bug relied on is real (so the seed
16
+ * is what prevents it).
17
+ */
18
+
19
+ const buildArgs: (
20
+ monitorType: MonitorType,
21
+ ) => Parameters<typeof MonitorStep.getDefaultMonitorStep>[0] = (
22
+ monitorType: MonitorType,
23
+ ) => {
24
+ const id: ObjectID = ObjectID.generate();
25
+ return {
26
+ monitorName: `${monitorType} monitor`,
27
+ monitorType,
28
+ onlineMonitorStatusId: id,
29
+ offlineMonitorStatusId: id,
30
+ defaultIncidentSeverityId: id,
31
+ defaultAlertSeverityId: id,
32
+ };
33
+ };
34
+
35
+ const serializedValue: (step: MonitorStep) => JSONObject = (
36
+ step: MonitorStep,
37
+ ): JSONObject => {
38
+ return step.toJSON()["value"] as JSONObject;
39
+ };
40
+
41
+ const roundTrip: (step: MonitorStep) => MonitorStep = (
42
+ step: MonitorStep,
43
+ ): MonitorStep => {
44
+ return MonitorStep.fromJSON(step.toJSON());
45
+ };
46
+
47
+ describe("MonitorStep telemetry sub-config serialization round-trip", () => {
48
+ test("a default Logs step keeps its logMonitor through toJSON/fromJSON", () => {
49
+ const step: MonitorStep = MonitorStep.getDefaultMonitorStep(
50
+ buildArgs(MonitorType.Logs),
51
+ );
52
+
53
+ // Serialized output must actually contain the sub-config...
54
+ const logMonitorJson: JSONObject = serializedValue(step)[
55
+ "logMonitor"
56
+ ] as JSONObject;
57
+ expect(logMonitorJson).toBeDefined();
58
+ expect(logMonitorJson["lastXSecondsOfLogs"]).toBeGreaterThan(0);
59
+
60
+ // ...and it must survive a round-trip back into a MonitorStep.
61
+ expect(roundTrip(step).data?.logMonitor).toBeDefined();
62
+ });
63
+
64
+ test.each([
65
+ [MonitorType.Traces, "traceMonitor"],
66
+ [MonitorType.Metrics, "metricMonitor"],
67
+ [MonitorType.Exceptions, "exceptionMonitor"],
68
+ ])(
69
+ "a default %s step keeps its %s",
70
+ (monitorType: MonitorType, key: string) => {
71
+ const step: MonitorStep = MonitorStep.getDefaultMonitorStep(
72
+ buildArgs(monitorType),
73
+ );
74
+
75
+ expect(serializedValue(step)[key]).toBeDefined();
76
+ expect(
77
+ (roundTrip(step).data as unknown as JSONObject)[key],
78
+ ).toBeDefined();
79
+ },
80
+ );
81
+
82
+ test("user-entered log filters are written to the persisted form", () => {
83
+ const serviceId: ObjectID = ObjectID.generate();
84
+ const step: MonitorStep = MonitorStep.getDefaultMonitorStep(
85
+ buildArgs(MonitorType.Logs),
86
+ );
87
+
88
+ const custom: MonitorStepLogMonitor = {
89
+ attributes: { env: "prod" },
90
+ body: "connection refused",
91
+ severityTexts: [LogSeverity.Error, LogSeverity.Fatal],
92
+ telemetryServiceIds: [serviceId],
93
+ entityKeys: ["host-1"],
94
+ lastXSecondsOfLogs: 300,
95
+ };
96
+ step.setLogMonitor(custom);
97
+
98
+ // The canonical persisted form (toJSON) carries every filter the user set.
99
+ const logMonitorJson: JSONObject = serializedValue(step)[
100
+ "logMonitor"
101
+ ] as JSONObject;
102
+ expect(logMonitorJson["body"]).toBe("connection refused");
103
+ expect(logMonitorJson["severityTexts"]).toEqual([
104
+ LogSeverity.Error,
105
+ LogSeverity.Fatal,
106
+ ]);
107
+ expect(logMonitorJson["attributes"]).toEqual({ env: "prod" });
108
+ expect(logMonitorJson["entityKeys"]).toEqual(["host-1"]);
109
+ expect(logMonitorJson["lastXSecondsOfLogs"]).toBe(300);
110
+ expect(
111
+ (logMonitorJson["telemetryServiceIds"] as Array<unknown>).length,
112
+ ).toBe(1);
113
+
114
+ // And the config still deserializes back onto the step.
115
+ expect(roundTrip(step).data?.logMonitor).toBeDefined();
116
+ });
117
+
118
+ test("the drop-when-undefined behavior the bug relied on is real", () => {
119
+ /*
120
+ * A Ping monitor has no telemetry sub-config. Its serialized form must
121
+ * not carry logMonitor/traceMonitor/etc, and the round-trip returns
122
+ * undefined for them. This is exactly why leaving the config undefined
123
+ * (pre-fix) lost it on save — and why seeding it (the fix) is required.
124
+ */
125
+ const step: MonitorStep = MonitorStep.getDefaultMonitorStep(
126
+ buildArgs(MonitorType.Ping),
127
+ );
128
+
129
+ const value: JSONObject = serializedValue(step);
130
+ expect(value["logMonitor"]).toBeUndefined();
131
+ expect(value["traceMonitor"]).toBeUndefined();
132
+ expect(value["metricMonitor"]).toBeUndefined();
133
+ expect(value["exceptionMonitor"]).toBeUndefined();
134
+
135
+ const restored: MonitorStep = roundTrip(step);
136
+ expect(restored.data?.logMonitor).toBeUndefined();
137
+ expect(restored.data?.traceMonitor).toBeUndefined();
138
+ expect(restored.data?.metricMonitor).toBeUndefined();
139
+ expect(restored.data?.exceptionMonitor).toBeUndefined();
140
+ });
141
+ });
@@ -0,0 +1,137 @@
1
+ import Span, { SpanStatus } from "../../../Models/AnalyticsModels/Span";
2
+ import Includes from "../../../Types/BaseDatabase/Includes";
3
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
4
+ import Query from "../../../Types/BaseDatabase/Query";
5
+ import Search from "../../../Types/BaseDatabase/Search";
6
+ import MonitorStepTraceMonitor, {
7
+ MonitorStepTraceMonitorUtil,
8
+ } from "../../../Types/Monitor/MonitorStepTraceMonitor";
9
+ import ObjectID from "../../../Types/ObjectID";
10
+
11
+ /*
12
+ * Parity coverage for the trace monitor step util. monitorTrace() now falls
13
+ * back to MonitorStepTraceMonitorUtil.getDefault() when a step was saved with
14
+ * no config, so getDefault() must produce a query that toQuery() turns into a
15
+ * valid, minimal Span query (just a trailing time window).
16
+ */
17
+ describe("MonitorStepTraceMonitorUtil", () => {
18
+ describe("getDefault", () => {
19
+ test("returns an empty monitor scoped to the last 60 seconds", () => {
20
+ const def: MonitorStepTraceMonitor =
21
+ MonitorStepTraceMonitorUtil.getDefault();
22
+
23
+ expect(def.attributes).toEqual({});
24
+ expect(def.spanName).toBe("");
25
+ expect(def.spanStatuses).toEqual([]);
26
+ expect(def.telemetryServiceIds).toEqual([]);
27
+ expect(def.entityKeys).toEqual([]);
28
+ expect(def.lastXSecondsOfSpans).toBe(60);
29
+ });
30
+
31
+ test("toQuery(getDefault()) is a valid minimal query (time window only)", () => {
32
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery(
33
+ MonitorStepTraceMonitorUtil.getDefault(),
34
+ );
35
+
36
+ // Only the trailing time window is set; no over-scoping filters.
37
+ expect(query.startTime).toBeInstanceOf(InBetween);
38
+ expect(query.primaryEntityId).toBeUndefined();
39
+ expect(query.entityKeys).toBeUndefined();
40
+ expect(query.attributes).toBeUndefined();
41
+ expect(query.statusCode).toBeUndefined();
42
+ expect(query.name).toBeUndefined();
43
+ });
44
+ });
45
+
46
+ describe("toQuery", () => {
47
+ test("builds no filters for an empty monitor with the time window off", () => {
48
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery({
49
+ ...MonitorStepTraceMonitorUtil.getDefault(),
50
+ lastXSecondsOfSpans: 0,
51
+ });
52
+ expect(query).toEqual({});
53
+ });
54
+
55
+ test("scopes to telemetry services when provided", () => {
56
+ const serviceId: ObjectID = ObjectID.generate();
57
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery({
58
+ ...MonitorStepTraceMonitorUtil.getDefault(),
59
+ telemetryServiceIds: [serviceId],
60
+ lastXSecondsOfSpans: 0,
61
+ });
62
+ expect(query.primaryEntityId).toBeInstanceOf(Includes);
63
+ expect((query.primaryEntityId as Includes).values).toEqual([serviceId]);
64
+ });
65
+
66
+ test("scopes to entity keys when provided", () => {
67
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery({
68
+ ...MonitorStepTraceMonitorUtil.getDefault(),
69
+ entityKeys: ["host-1"],
70
+ lastXSecondsOfSpans: 0,
71
+ });
72
+ expect(query.entityKeys).toBeInstanceOf(Includes);
73
+ expect((query.entityKeys as Includes).values).toEqual(["host-1"]);
74
+ });
75
+
76
+ test("filters by span status when provided", () => {
77
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery({
78
+ ...MonitorStepTraceMonitorUtil.getDefault(),
79
+ spanStatuses: [SpanStatus.Error],
80
+ lastXSecondsOfSpans: 0,
81
+ });
82
+ expect(query.statusCode).toBeInstanceOf(Includes);
83
+ expect((query.statusCode as Includes).values).toEqual([SpanStatus.Error]);
84
+ });
85
+
86
+ test("searches the span name when provided", () => {
87
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery({
88
+ ...MonitorStepTraceMonitorUtil.getDefault(),
89
+ spanName: "GET /checkout",
90
+ lastXSecondsOfSpans: 0,
91
+ });
92
+ expect(query.name).toBeInstanceOf(Search);
93
+ expect((query.name as Search<string>).value).toBe("GET /checkout");
94
+ });
95
+
96
+ test("builds a trailing time window from lastXSecondsOfSpans", () => {
97
+ const query: Query<Span> = MonitorStepTraceMonitorUtil.toQuery({
98
+ ...MonitorStepTraceMonitorUtil.getDefault(),
99
+ lastXSecondsOfSpans: 120,
100
+ });
101
+ expect(query.startTime).toBeInstanceOf(InBetween);
102
+ const time: InBetween<Date> = query.startTime as InBetween<Date>;
103
+ const spanMs: number =
104
+ time.endValue.getTime() - time.startValue.getTime();
105
+ expect(Math.abs(spanMs - 120 * 1000)).toBeLessThan(2000);
106
+ });
107
+ });
108
+
109
+ describe("toJSON / fromJSON", () => {
110
+ test("round-trips a fully populated monitor", () => {
111
+ const original: MonitorStepTraceMonitor = {
112
+ attributes: { env: "prod" },
113
+ spanName: "GET /checkout",
114
+ spanStatuses: [SpanStatus.Error, SpanStatus.Ok],
115
+ telemetryServiceIds: [ObjectID.generate()],
116
+ entityKeys: ["host-1"],
117
+ lastXSecondsOfSpans: 300,
118
+ };
119
+
120
+ const roundTripped: MonitorStepTraceMonitor =
121
+ MonitorStepTraceMonitorUtil.fromJSON(
122
+ MonitorStepTraceMonitorUtil.toJSON(original),
123
+ );
124
+
125
+ expect(roundTripped.attributes).toEqual(original.attributes);
126
+ expect(roundTripped.spanName).toBe(original.spanName);
127
+ expect(roundTripped.spanStatuses).toEqual(original.spanStatuses);
128
+ expect(roundTripped.entityKeys).toEqual(original.entityKeys);
129
+ expect(roundTripped.lastXSecondsOfSpans).toBe(
130
+ original.lastXSecondsOfSpans,
131
+ );
132
+ expect(roundTripped.telemetryServiceIds.map(String)).toEqual(
133
+ original.telemetryServiceIds.map(String),
134
+ );
135
+ });
136
+ });
137
+ });