@oneuptime/common 10.0.65 → 10.0.66

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 (210) hide show
  1. package/Models/DatabaseModels/DockerHostOwnerTeam.ts +464 -0
  2. package/Models/DatabaseModels/DockerHostOwnerUser.ts +463 -0
  3. package/Models/DatabaseModels/Index.ts +24 -0
  4. package/Models/DatabaseModels/KubernetesClusterOwnerTeam.ts +464 -0
  5. package/Models/DatabaseModels/KubernetesClusterOwnerUser.ts +463 -0
  6. package/Models/DatabaseModels/KubernetesResource.ts +548 -0
  7. package/Models/DatabaseModels/MetricPipelineRule.ts +804 -0
  8. package/Models/DatabaseModels/MetricRecordingRule.ts +470 -0
  9. package/Models/DatabaseModels/Monitor.ts +2 -0
  10. package/Models/DatabaseModels/Project.ts +53 -0
  11. package/Models/DatabaseModels/Service.ts +79 -0
  12. package/Models/DatabaseModels/TraceDropFilter.ts +508 -0
  13. package/Models/DatabaseModels/TracePipeline.ts +436 -0
  14. package/Models/DatabaseModels/TracePipelineProcessor.ts +454 -0
  15. package/Models/DatabaseModels/TraceRecordingRule.ts +470 -0
  16. package/Models/DatabaseModels/TraceScrubRule.ts +546 -0
  17. package/Server/API/KubernetesResourceAPI.ts +129 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName.ts +399 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1776505976155-AddTracePipelineTables.ts +205 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1776509413763-MigrationName.ts +335 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1776541018853-MigrationName.ts +29 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1776544084793-MigrationName.ts +53 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -1
  24. package/Server/Services/DockerHostOwnerTeamService.ts +10 -0
  25. package/Server/Services/DockerHostOwnerUserService.ts +10 -0
  26. package/Server/Services/KubernetesClusterOwnerTeamService.ts +10 -0
  27. package/Server/Services/KubernetesClusterOwnerUserService.ts +10 -0
  28. package/Server/Services/KubernetesResourceService.ts +351 -0
  29. package/Server/Services/MetricPipelineRuleService.ts +10 -0
  30. package/Server/Services/MetricRecordingRuleService.ts +10 -0
  31. package/Server/Services/TraceDropFilterService.ts +10 -0
  32. package/Server/Services/TracePipelineProcessorService.ts +10 -0
  33. package/Server/Services/TracePipelineService.ts +10 -0
  34. package/Server/Services/TraceRecordingRuleService.ts +10 -0
  35. package/Server/Services/TraceScrubRuleService.ts +10 -0
  36. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +71 -9
  37. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +483 -75
  38. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +379 -6
  39. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +502 -0
  40. package/Tests/Utils/MetricUnitUtil.test.ts +216 -0
  41. package/Tests/Utils/Metrics/MetricFormulaEvaluator.test.ts +269 -0
  42. package/Tests/Utils/Metrics/MetricResultUnitConverter.test.ts +231 -0
  43. package/Tests/Utils/RecordingRuleExpression.test.ts +177 -0
  44. package/Types/Kubernetes/KubernetesInventoryExtractor.ts +327 -0
  45. package/Types/Kubernetes/KubernetesObjectParser.ts +1949 -0
  46. package/Types/Metrics/MetricDownsamplingRetentionDays.ts +49 -0
  47. package/Types/Metrics/MetricFormulaConfigData.ts +4 -0
  48. package/Types/Metrics/MetricPipelineRuleFilterCondition.ts +136 -0
  49. package/Types/Metrics/MetricPipelineRuleType.ts +27 -0
  50. package/Types/Metrics/RecordingRuleDefinition.ts +180 -0
  51. package/Types/Monitor/CriteriaFilter.ts +43 -0
  52. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +70 -0
  53. package/Types/Permission.ts +520 -0
  54. package/Types/Trace/TraceAggregationType.ts +17 -0
  55. package/Types/Trace/TraceDropFilterAction.ts +6 -0
  56. package/Types/Trace/TracePipelineProcessorType.ts +56 -0
  57. package/Types/Trace/TraceRecordingRuleDefinition.ts +218 -0
  58. package/Types/Trace/TraceScrubAction.ts +7 -0
  59. package/Types/Trace/TraceScrubField.ts +8 -0
  60. package/Types/Trace/TraceScrubPatternType.ts +10 -0
  61. package/UI/Components/CardSelect/CardSelect.tsx +9 -1
  62. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +6 -10
  63. package/UI/Components/Forms/Fields/FormField.tsx +1 -0
  64. package/UI/Components/Forms/Types/Field.ts +1 -0
  65. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +57 -0
  66. package/UI/Components/Page/Page.tsx +6 -0
  67. package/Utils/MetricUnitUtil.ts +289 -0
  68. package/Utils/Metrics/MetricFormulaEvaluator.ts +610 -0
  69. package/Utils/Metrics/MetricResultUnitConverter.ts +91 -0
  70. package/Utils/Metrics/RecordingRuleExpression.ts +359 -0
  71. package/Utils/ValueFormatter.ts +137 -13
  72. package/build/dist/Models/DatabaseModels/DockerHostOwnerTeam.js +480 -0
  73. package/build/dist/Models/DatabaseModels/DockerHostOwnerTeam.js.map +1 -0
  74. package/build/dist/Models/DatabaseModels/DockerHostOwnerUser.js +479 -0
  75. package/build/dist/Models/DatabaseModels/DockerHostOwnerUser.js.map +1 -0
  76. package/build/dist/Models/DatabaseModels/Index.js +24 -0
  77. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  78. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerTeam.js +480 -0
  79. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerTeam.js.map +1 -0
  80. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerUser.js +479 -0
  81. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerUser.js.map +1 -0
  82. package/build/dist/Models/DatabaseModels/KubernetesResource.js +590 -0
  83. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -0
  84. package/build/dist/Models/DatabaseModels/MetricPipelineRule.js +836 -0
  85. package/build/dist/Models/DatabaseModels/MetricPipelineRule.js.map +1 -0
  86. package/build/dist/Models/DatabaseModels/MetricRecordingRule.js +497 -0
  87. package/build/dist/Models/DatabaseModels/MetricRecordingRule.js.map +1 -0
  88. package/build/dist/Models/DatabaseModels/Monitor.js +2 -0
  89. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/Project.js +53 -0
  91. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/Service.js +79 -0
  93. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +536 -0
  95. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -0
  96. package/build/dist/Models/DatabaseModels/TracePipeline.js +462 -0
  97. package/build/dist/Models/DatabaseModels/TracePipeline.js.map +1 -0
  98. package/build/dist/Models/DatabaseModels/TracePipelineProcessor.js +476 -0
  99. package/build/dist/Models/DatabaseModels/TracePipelineProcessor.js.map +1 -0
  100. package/build/dist/Models/DatabaseModels/TraceRecordingRule.js +497 -0
  101. package/build/dist/Models/DatabaseModels/TraceRecordingRule.js.map +1 -0
  102. package/build/dist/Models/DatabaseModels/TraceScrubRule.js +575 -0
  103. package/build/dist/Models/DatabaseModels/TraceScrubRule.js.map +1 -0
  104. package/build/dist/Server/API/KubernetesResourceAPI.js +98 -0
  105. package/build/dist/Server/API/KubernetesResourceAPI.js.map +1 -0
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName.js +144 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776505976155-AddTracePipelineTables.js +82 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776505976155-AddTracePipelineTables.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776509413763-MigrationName.js +118 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776509413763-MigrationName.js.map +1 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776541018853-MigrationName.js +16 -0
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776541018853-MigrationName.js.map +1 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776544084793-MigrationName.js +24 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776544084793-MigrationName.js.map +1 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  118. package/build/dist/Server/Services/DockerHostOwnerTeamService.js +9 -0
  119. package/build/dist/Server/Services/DockerHostOwnerTeamService.js.map +1 -0
  120. package/build/dist/Server/Services/DockerHostOwnerUserService.js +9 -0
  121. package/build/dist/Server/Services/DockerHostOwnerUserService.js.map +1 -0
  122. package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js +9 -0
  123. package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js.map +1 -0
  124. package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js +9 -0
  125. package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js.map +1 -0
  126. package/build/dist/Server/Services/KubernetesResourceService.js +237 -0
  127. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -0
  128. package/build/dist/Server/Services/MetricPipelineRuleService.js +9 -0
  129. package/build/dist/Server/Services/MetricPipelineRuleService.js.map +1 -0
  130. package/build/dist/Server/Services/MetricRecordingRuleService.js +9 -0
  131. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -0
  132. package/build/dist/Server/Services/TraceDropFilterService.js +9 -0
  133. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -0
  134. package/build/dist/Server/Services/TracePipelineProcessorService.js +9 -0
  135. package/build/dist/Server/Services/TracePipelineProcessorService.js.map +1 -0
  136. package/build/dist/Server/Services/TracePipelineService.js +9 -0
  137. package/build/dist/Server/Services/TracePipelineService.js.map +1 -0
  138. package/build/dist/Server/Services/TraceRecordingRuleService.js +9 -0
  139. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -0
  140. package/build/dist/Server/Services/TraceScrubRuleService.js +9 -0
  141. package/build/dist/Server/Services/TraceScrubRuleService.js.map +1 -0
  142. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +56 -9
  143. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  144. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +335 -53
  145. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  146. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +277 -5
  147. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  148. package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js +407 -0
  149. package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js.map +1 -0
  150. package/build/dist/Tests/Utils/MetricUnitUtil.test.js +159 -0
  151. package/build/dist/Tests/Utils/MetricUnitUtil.test.js.map +1 -0
  152. package/build/dist/Tests/Utils/Metrics/MetricFormulaEvaluator.test.js +224 -0
  153. package/build/dist/Tests/Utils/Metrics/MetricFormulaEvaluator.test.js.map +1 -0
  154. package/build/dist/Tests/Utils/Metrics/MetricResultUnitConverter.test.js +180 -0
  155. package/build/dist/Tests/Utils/Metrics/MetricResultUnitConverter.test.js.map +1 -0
  156. package/build/dist/Tests/Utils/RecordingRuleExpression.test.js +142 -0
  157. package/build/dist/Tests/Utils/RecordingRuleExpression.test.js.map +1 -0
  158. package/build/dist/Types/Kubernetes/KubernetesInventoryExtractor.js +200 -0
  159. package/build/dist/Types/Kubernetes/KubernetesInventoryExtractor.js.map +1 -0
  160. package/build/dist/Types/Kubernetes/KubernetesObjectParser.js +1205 -0
  161. package/build/dist/Types/Kubernetes/KubernetesObjectParser.js.map +1 -0
  162. package/build/dist/Types/Metrics/MetricDownsamplingRetentionDays.js +32 -0
  163. package/build/dist/Types/Metrics/MetricDownsamplingRetentionDays.js.map +1 -0
  164. package/build/dist/Types/Metrics/MetricPipelineRuleFilterCondition.js +103 -0
  165. package/build/dist/Types/Metrics/MetricPipelineRuleFilterCondition.js.map +1 -0
  166. package/build/dist/Types/Metrics/MetricPipelineRuleType.js +27 -0
  167. package/build/dist/Types/Metrics/MetricPipelineRuleType.js.map +1 -0
  168. package/build/dist/Types/Metrics/RecordingRuleDefinition.js +110 -0
  169. package/build/dist/Types/Metrics/RecordingRuleDefinition.js.map +1 -0
  170. package/build/dist/Types/Monitor/CriteriaFilter.js +22 -0
  171. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  172. package/build/dist/Types/Monitor/MetricMonitor/MetricCriteriaContext.js +2 -0
  173. package/build/dist/Types/Monitor/MetricMonitor/MetricCriteriaContext.js.map +1 -0
  174. package/build/dist/Types/Permission.js +454 -0
  175. package/build/dist/Types/Permission.js.map +1 -1
  176. package/build/dist/Types/Trace/TraceAggregationType.js +18 -0
  177. package/build/dist/Types/Trace/TraceAggregationType.js.map +1 -0
  178. package/build/dist/Types/Trace/TraceDropFilterAction.js +7 -0
  179. package/build/dist/Types/Trace/TraceDropFilterAction.js.map +1 -0
  180. package/build/dist/Types/Trace/TracePipelineProcessorType.js +10 -0
  181. package/build/dist/Types/Trace/TracePipelineProcessorType.js.map +1 -0
  182. package/build/dist/Types/Trace/TraceRecordingRuleDefinition.js +145 -0
  183. package/build/dist/Types/Trace/TraceRecordingRuleDefinition.js.map +1 -0
  184. package/build/dist/Types/Trace/TraceScrubAction.js +8 -0
  185. package/build/dist/Types/Trace/TraceScrubAction.js.map +1 -0
  186. package/build/dist/Types/Trace/TraceScrubField.js +9 -0
  187. package/build/dist/Types/Trace/TraceScrubField.js.map +1 -0
  188. package/build/dist/Types/Trace/TraceScrubPatternType.js +11 -0
  189. package/build/dist/Types/Trace/TraceScrubPatternType.js.map +1 -0
  190. package/build/dist/UI/Components/CardSelect/CardSelect.js +3 -1
  191. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -9
  193. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  194. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  195. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  196. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +30 -0
  197. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  198. package/build/dist/UI/Components/Page/Page.js +1 -0
  199. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  200. package/build/dist/Utils/MetricUnitUtil.js +232 -0
  201. package/build/dist/Utils/MetricUnitUtil.js.map +1 -0
  202. package/build/dist/Utils/Metrics/MetricFormulaEvaluator.js +453 -0
  203. package/build/dist/Utils/Metrics/MetricFormulaEvaluator.js.map +1 -0
  204. package/build/dist/Utils/Metrics/MetricResultUnitConverter.js +61 -0
  205. package/build/dist/Utils/Metrics/MetricResultUnitConverter.js.map +1 -0
  206. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +298 -0
  207. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -0
  208. package/build/dist/Utils/ValueFormatter.js +123 -13
  209. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  210. package/package.json +1 -1
@@ -0,0 +1,359 @@
1
+ /*
2
+ * Recording-rule expression DSL.
3
+ *
4
+ * Grammar (recursive descent):
5
+ *
6
+ * expr := term (('+' | '-') term)*
7
+ * term := factor (('*' | '/') factor)*
8
+ * factor := number | identifier | '(' expr ')' | '-' factor
9
+ * number := digits ('.' digits)?
10
+ * identifier := [A-Za-z_][A-Za-z0-9_]*
11
+ *
12
+ * Evaluation returns `null` when the result is not a finite real number —
13
+ * division by zero, missing binding, overflow, or NaN. Callers treat `null`
14
+ * as "skip this bucket".
15
+ *
16
+ * Intentionally small: no function calls, no power operator, no comparisons.
17
+ * A future version can grow this DSL without breaking stored rules because
18
+ * the whole expression is one string column on the model.
19
+ */
20
+
21
+ const MAX_DEPTH: number = 32;
22
+
23
+ export interface Token {
24
+ type: "number" | "ident" | "op" | "lparen" | "rparen";
25
+ value: string;
26
+ pos: number;
27
+ }
28
+
29
+ export type Node =
30
+ | { type: "num"; value: number }
31
+ | { type: "ident"; name: string }
32
+ | { type: "unary"; op: "-"; operand: Node }
33
+ | { type: "binary"; op: "+" | "-" | "*" | "/"; left: Node; right: Node };
34
+
35
+ export interface ParseResult {
36
+ ok: true;
37
+ ast: Node;
38
+ identifiers: Array<string>;
39
+ }
40
+
41
+ export interface ParseError {
42
+ ok: false;
43
+ error: string;
44
+ position?: number;
45
+ }
46
+
47
+ export interface ValidateResult {
48
+ ok: boolean;
49
+ error?: string;
50
+ position?: number;
51
+ unknownIdentifiers?: Array<string>;
52
+ }
53
+
54
+ export function tokenize(input: string): Array<Token> | ParseError {
55
+ const tokens: Array<Token> = [];
56
+ let i: number = 0;
57
+ while (i < input.length) {
58
+ const ch: string = input[i] as string;
59
+ if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
60
+ i++;
61
+ continue;
62
+ }
63
+ if (ch === "(") {
64
+ tokens.push({ type: "lparen", value: ch, pos: i });
65
+ i++;
66
+ continue;
67
+ }
68
+ if (ch === ")") {
69
+ tokens.push({ type: "rparen", value: ch, pos: i });
70
+ i++;
71
+ continue;
72
+ }
73
+ if (ch === "+" || ch === "-" || ch === "*" || ch === "/") {
74
+ tokens.push({ type: "op", value: ch, pos: i });
75
+ i++;
76
+ continue;
77
+ }
78
+ if (ch >= "0" && ch <= "9") {
79
+ const start: number = i;
80
+ while (i < input.length && input[i]! >= "0" && input[i]! <= "9") {
81
+ i++;
82
+ }
83
+ if (input[i] === ".") {
84
+ i++;
85
+ while (i < input.length && input[i]! >= "0" && input[i]! <= "9") {
86
+ i++;
87
+ }
88
+ }
89
+ tokens.push({ type: "number", value: input.slice(start, i), pos: start });
90
+ continue;
91
+ }
92
+ if (isIdentStart(ch)) {
93
+ const start: number = i;
94
+ i++;
95
+ while (i < input.length && isIdentPart(input[i] as string)) {
96
+ i++;
97
+ }
98
+ tokens.push({ type: "ident", value: input.slice(start, i), pos: start });
99
+ continue;
100
+ }
101
+ return {
102
+ ok: false,
103
+ error: `Unexpected character "${ch}" at position ${i}`,
104
+ position: i,
105
+ };
106
+ }
107
+ return tokens;
108
+ }
109
+
110
+ function isIdentStart(ch: string): boolean {
111
+ return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch === "_";
112
+ }
113
+
114
+ function isIdentPart(ch: string): boolean {
115
+ return isIdentStart(ch) || (ch >= "0" && ch <= "9");
116
+ }
117
+
118
+ // Recursive-descent parser.
119
+ export function parse(input: string): ParseResult | ParseError {
120
+ const tokenized: Array<Token> | ParseError = tokenize(input);
121
+ if (!Array.isArray(tokenized)) {
122
+ return tokenized;
123
+ }
124
+
125
+ const tokens: Array<Token> = tokenized;
126
+ const state: { i: number; depth: number } = { i: 0, depth: 0 };
127
+ const identifiers: Set<string> = new Set();
128
+
129
+ function peek(): Token | undefined {
130
+ return tokens[state.i];
131
+ }
132
+
133
+ function advance(): Token | undefined {
134
+ return tokens[state.i++];
135
+ }
136
+
137
+ function parseExpr(): Node | ParseError {
138
+ if (state.depth > MAX_DEPTH) {
139
+ return { ok: false, error: "Expression nesting too deep" };
140
+ }
141
+ state.depth++;
142
+ let left: Node | ParseError = parseTerm();
143
+ if ("ok" in left && left.ok === false) {
144
+ state.depth--;
145
+ return left;
146
+ }
147
+ while (true) {
148
+ const next: Token | undefined = peek();
149
+ if (next?.type === "op" && (next.value === "+" || next.value === "-")) {
150
+ advance();
151
+ const right: Node | ParseError = parseTerm();
152
+ if ("ok" in right && right.ok === false) {
153
+ state.depth--;
154
+ return right;
155
+ }
156
+ left = {
157
+ type: "binary",
158
+ op: next.value as "+" | "-",
159
+ left: left as Node,
160
+ right: right as Node,
161
+ };
162
+ continue;
163
+ }
164
+ break;
165
+ }
166
+ state.depth--;
167
+ return left as Node;
168
+ }
169
+
170
+ function parseTerm(): Node | ParseError {
171
+ let left: Node | ParseError = parseFactor();
172
+ if ("ok" in left && left.ok === false) {
173
+ return left;
174
+ }
175
+ while (true) {
176
+ const next: Token | undefined = peek();
177
+ if (next?.type === "op" && (next.value === "*" || next.value === "/")) {
178
+ advance();
179
+ const right: Node | ParseError = parseFactor();
180
+ if ("ok" in right && right.ok === false) {
181
+ return right;
182
+ }
183
+ left = {
184
+ type: "binary",
185
+ op: next.value as "*" | "/",
186
+ left: left as Node,
187
+ right: right as Node,
188
+ };
189
+ continue;
190
+ }
191
+ break;
192
+ }
193
+ return left as Node;
194
+ }
195
+
196
+ function parseFactor(): Node | ParseError {
197
+ const tok: Token | undefined = peek();
198
+ if (!tok) {
199
+ return {
200
+ ok: false,
201
+ error: "Unexpected end of expression",
202
+ };
203
+ }
204
+ if (tok.type === "op" && tok.value === "-") {
205
+ advance();
206
+ const operand: Node | ParseError = parseFactor();
207
+ if ("ok" in operand && operand.ok === false) {
208
+ return operand;
209
+ }
210
+ return { type: "unary", op: "-", operand: operand as Node };
211
+ }
212
+ if (tok.type === "number") {
213
+ advance();
214
+ const n: number = Number(tok.value);
215
+ if (!Number.isFinite(n)) {
216
+ return {
217
+ ok: false,
218
+ error: `Invalid number "${tok.value}"`,
219
+ position: tok.pos,
220
+ };
221
+ }
222
+ return { type: "num", value: n };
223
+ }
224
+ if (tok.type === "ident") {
225
+ advance();
226
+ identifiers.add(tok.value);
227
+ return { type: "ident", name: tok.value };
228
+ }
229
+ if (tok.type === "lparen") {
230
+ advance();
231
+ const inner: Node | ParseError = parseExpr();
232
+ if ("ok" in inner && inner.ok === false) {
233
+ return inner;
234
+ }
235
+ const close: Token | undefined = advance();
236
+ if (!close || close.type !== "rparen") {
237
+ return {
238
+ ok: false,
239
+ error: "Expected ')'",
240
+ ...(close ? { position: close.pos } : {}),
241
+ };
242
+ }
243
+ return inner as Node;
244
+ }
245
+ return {
246
+ ok: false,
247
+ error: `Unexpected token "${tok.value}"`,
248
+ position: tok.pos,
249
+ };
250
+ }
251
+
252
+ const ast: Node | ParseError = parseExpr();
253
+ if ("ok" in ast && ast.ok === false) {
254
+ return ast;
255
+ }
256
+ if (state.i !== tokens.length) {
257
+ const stray: Token | undefined = tokens[state.i];
258
+ return {
259
+ ok: false,
260
+ error: `Unexpected token "${stray?.value ?? ""}" at position ${stray?.pos ?? -1}`,
261
+ ...(stray ? { position: stray.pos } : {}),
262
+ };
263
+ }
264
+ return {
265
+ ok: true,
266
+ ast: ast as Node,
267
+ identifiers: Array.from(identifiers),
268
+ };
269
+ }
270
+
271
+ /*
272
+ * Evaluate an AST against the given variable bindings. Returns null when
273
+ * the result is not a finite real number (division by zero, missing binding,
274
+ * NaN, Infinity). The caller skips any output row whose result is null.
275
+ */
276
+ export function evaluate(
277
+ node: Node,
278
+ bindings: Record<string, number>,
279
+ ): number | null {
280
+ switch (node.type) {
281
+ case "num":
282
+ return Number.isFinite(node.value) ? node.value : null;
283
+ case "ident": {
284
+ const v: number | undefined = bindings[node.name];
285
+ if (typeof v !== "number" || !Number.isFinite(v)) {
286
+ return null;
287
+ }
288
+ return v;
289
+ }
290
+ case "unary": {
291
+ const inner: number | null = evaluate(node.operand, bindings);
292
+ if (inner === null) {
293
+ return null;
294
+ }
295
+ return -inner;
296
+ }
297
+ case "binary": {
298
+ const l: number | null = evaluate(node.left, bindings);
299
+ if (l === null) {
300
+ return null;
301
+ }
302
+ const r: number | null = evaluate(node.right, bindings);
303
+ if (r === null) {
304
+ return null;
305
+ }
306
+ let out: number;
307
+ switch (node.op) {
308
+ case "+":
309
+ out = l + r;
310
+ break;
311
+ case "-":
312
+ out = l - r;
313
+ break;
314
+ case "*":
315
+ out = l * r;
316
+ break;
317
+ case "/":
318
+ if (r === 0) {
319
+ return null;
320
+ }
321
+ out = l / r;
322
+ break;
323
+ }
324
+ return Number.isFinite(out) ? out : null;
325
+ }
326
+ }
327
+ }
328
+
329
+ /*
330
+ * Parse + check that every identifier in the expression is in `allowed`.
331
+ * Used at rule-save time so the form can fail fast before the cron runs.
332
+ */
333
+ export function validate(
334
+ expression: string,
335
+ allowed: Array<string>,
336
+ ): ValidateResult {
337
+ const parsed: ParseResult | ParseError = parse(expression);
338
+ if (!parsed.ok) {
339
+ return {
340
+ ok: false,
341
+ error: parsed.error,
342
+ ...(typeof parsed.position === "number"
343
+ ? { position: parsed.position }
344
+ : {}),
345
+ };
346
+ }
347
+ const allowedSet: Set<string> = new Set(allowed);
348
+ const unknown: Array<string> = parsed.identifiers.filter((id: string) => {
349
+ return !allowedSet.has(id);
350
+ });
351
+ if (unknown.length > 0) {
352
+ return {
353
+ ok: false,
354
+ error: `Unknown identifier(s): ${unknown.join(", ")}`,
355
+ unknownIdentifiers: unknown,
356
+ };
357
+ }
358
+ return { ok: true };
359
+ }
@@ -25,34 +25,34 @@ const byteUnits: Array<UnitThreshold> = [
25
25
  ];
26
26
 
27
27
  const secondUnits: Array<UnitThreshold> = [
28
- { threshold: 86400, unit: "d", divisor: 86400 },
29
- { threshold: 3600, unit: "hr", divisor: 3600 },
28
+ { threshold: 86400, unit: "days", divisor: 86400 },
29
+ { threshold: 3600, unit: "hours", divisor: 3600 },
30
30
  { threshold: 60, unit: "min", divisor: 60 },
31
- { threshold: 1, unit: "s", divisor: 1 },
31
+ { threshold: 1, unit: "sec", divisor: 1 },
32
32
  { threshold: 0.001, unit: "ms", divisor: 0.001 },
33
33
  { threshold: 0.000001, unit: "µs", divisor: 0.000001 },
34
34
  { threshold: 0, unit: "ns", divisor: 0.000000001 },
35
35
  ];
36
36
 
37
37
  const millisecondUnits: Array<UnitThreshold> = [
38
- { threshold: 86400000, unit: "d", divisor: 86400000 },
39
- { threshold: 3600000, unit: "hr", divisor: 3600000 },
38
+ { threshold: 86400000, unit: "days", divisor: 86400000 },
39
+ { threshold: 3600000, unit: "hours", divisor: 3600000 },
40
40
  { threshold: 60000, unit: "min", divisor: 60000 },
41
- { threshold: 1000, unit: "s", divisor: 1000 },
41
+ { threshold: 1000, unit: "sec", divisor: 1000 },
42
42
  { threshold: 1, unit: "ms", divisor: 1 },
43
43
  { threshold: 0.001, unit: "µs", divisor: 0.001 },
44
44
  { threshold: 0, unit: "ns", divisor: 0.000001 },
45
45
  ];
46
46
 
47
47
  const microsecondUnits: Array<UnitThreshold> = [
48
- { threshold: 1e6, unit: "s", divisor: 1e6 },
48
+ { threshold: 1e6, unit: "sec", divisor: 1e6 },
49
49
  { threshold: 1e3, unit: "ms", divisor: 1e3 },
50
50
  { threshold: 1, unit: "µs", divisor: 1 },
51
51
  { threshold: 0, unit: "ns", divisor: 0.001 },
52
52
  ];
53
53
 
54
54
  const nanosecondUnits: Array<UnitThreshold> = [
55
- { threshold: 1e9, unit: "s", divisor: 1e9 },
55
+ { threshold: 1e9, unit: "sec", divisor: 1e9 },
56
56
  { threshold: 1e6, unit: "ms", divisor: 1e6 },
57
57
  { threshold: 1e3, unit: "µs", divisor: 1e3 },
58
58
  { threshold: 0, unit: "ns", divisor: 1 },
@@ -89,6 +89,100 @@ const unitTableMap: Record<string, Array<UnitThreshold>> = {
89
89
  ns: nanosecondUnits,
90
90
  };
91
91
 
92
+ /*
93
+ * Maps UCUM / OpenTelemetry unit codes to human-readable unit names.
94
+ * Used for labelling (e.g. badges) where the raw code "By" is hard to read.
95
+ */
96
+ const readableUnitMap: Record<string, string> = {
97
+ // Dimensionless — OpenTelemetry uses "1" for unitless counts
98
+ "1": "",
99
+
100
+ // Bytes (decimal)
101
+ by: "Bytes",
102
+ byte: "Bytes",
103
+ bytes: "Bytes",
104
+ b: "Bytes",
105
+ kby: "Kilobytes",
106
+ mby: "Megabytes",
107
+ gby: "Gigabytes",
108
+ tby: "Terabytes",
109
+ pby: "Petabytes",
110
+
111
+ // Bytes (binary)
112
+ kiby: "Kibibytes",
113
+ miby: "Mebibytes",
114
+ giby: "Gibibytes",
115
+ tiby: "Tebibytes",
116
+ piby: "Pebibytes",
117
+
118
+ // Bits
119
+ bit: "Bits",
120
+ bits: "Bits",
121
+ kbit: "Kilobits",
122
+ mbit: "Megabits",
123
+ gbit: "Gigabits",
124
+
125
+ // Time
126
+ ns: "Nanoseconds",
127
+ nanosecond: "Nanoseconds",
128
+ nanoseconds: "Nanoseconds",
129
+ us: "Microseconds",
130
+ µs: "Microseconds",
131
+ microsecond: "Microseconds",
132
+ microseconds: "Microseconds",
133
+ ms: "Milliseconds",
134
+ millisecond: "Milliseconds",
135
+ milliseconds: "Milliseconds",
136
+ s: "Seconds",
137
+ sec: "Seconds",
138
+ second: "Seconds",
139
+ seconds: "Seconds",
140
+ min: "Minutes",
141
+ minute: "Minutes",
142
+ minutes: "Minutes",
143
+ h: "Hours",
144
+ hr: "Hours",
145
+ hour: "Hours",
146
+ hours: "Hours",
147
+ d: "Days",
148
+ day: "Days",
149
+ days: "Days",
150
+
151
+ // Percent
152
+ "%": "Percent",
153
+ percent: "Percent",
154
+
155
+ // Frequency
156
+ hz: "Hertz",
157
+ khz: "Kilohertz",
158
+ mhz: "Megahertz",
159
+ ghz: "Gigahertz",
160
+
161
+ // Temperature
162
+ cel: "Celsius",
163
+ "[degf]": "Fahrenheit",
164
+ k: "Kelvin",
165
+
166
+ // Electrical
167
+ v: "Volts",
168
+ a: "Amperes",
169
+ w: "Watts",
170
+ kw: "Kilowatts",
171
+ j: "Joules",
172
+ };
173
+
174
+ function normalizeUnit(unit: string): string {
175
+ return unit.trim().toLowerCase();
176
+ }
177
+
178
+ function getReadableUnitPart(unit: string): string {
179
+ const normalized: string = normalizeUnit(unit);
180
+ if (readableUnitMap[normalized] !== undefined) {
181
+ return readableUnitMap[normalized]!;
182
+ }
183
+ return unit;
184
+ }
185
+
92
186
  function formatWithThresholds(
93
187
  value: number,
94
188
  thresholds: Array<UnitThreshold>,
@@ -144,11 +238,12 @@ export default class ValueFormatter {
144
238
  * e.g. formatValue(42, "%") → "42 %" (passthrough for unknown units)
145
239
  */
146
240
  public static formatValue(value: number, unit: string): string {
147
- if (!unit || unit.trim() === "") {
241
+ // OpenTelemetry uses "1" as the dimensionless marker — render as a bare number.
242
+ if (!unit || unit.trim() === "" || unit.trim() === "1") {
148
243
  return formatNumber(value);
149
244
  }
150
245
 
151
- const normalizedUnit: string = unit.trim().toLowerCase();
246
+ const normalizedUnit: string = normalizeUnit(unit);
152
247
  const thresholds: Array<UnitThreshold> | undefined =
153
248
  unitTableMap[normalizedUnit];
154
249
 
@@ -156,8 +251,8 @@ export default class ValueFormatter {
156
251
  return formatWithThresholds(value, thresholds).formatted;
157
252
  }
158
253
 
159
- // Unknown unit — just format the number and append the unit as-is
160
- return `${formatNumber(value)} ${unit}`;
254
+ // Unknown unit — format number and show the readable unit name when we have one.
255
+ return `${formatNumber(value)} ${ValueFormatter.getReadableUnit(unit)}`;
161
256
  }
162
257
 
163
258
  // Check if a unit is one we can auto-scale (bytes, seconds, etc.)
@@ -165,6 +260,35 @@ export default class ValueFormatter {
165
260
  if (!unit || unit.trim() === "") {
166
261
  return false;
167
262
  }
168
- return unitTableMap[unit.trim().toLowerCase()] !== undefined;
263
+ return unitTableMap[normalizeUnit(unit)] !== undefined;
264
+ }
265
+
266
+ /*
267
+ * Convert a UCUM / OpenTelemetry unit code into a human-readable name.
268
+ * e.g. "By" → "Bytes", "s" → "Seconds", "By/s" → "Bytes per Second",
269
+ * "1" → "" (dimensionless). Falls back to the original string when unknown.
270
+ */
271
+ public static getReadableUnit(unit: string): string {
272
+ if (!unit || unit.trim() === "" || unit.trim() === "1") {
273
+ return "";
274
+ }
275
+
276
+ // Handle compound rate units like "By/s" → "Bytes per Second"
277
+ if (unit.includes("/")) {
278
+ const parts: Array<string> = unit.split("/");
279
+ const readableParts: Array<string> = parts.map((part: string): string => {
280
+ return getReadableUnitPart(part);
281
+ });
282
+ // Singularize the denominator ("Seconds" → "Second") for nicer rate reading.
283
+ const [numerator, ...denominators] = readableParts;
284
+ const denominator: string = denominators
285
+ .map((d: string): string => {
286
+ return d.endsWith("s") ? d.slice(0, -1) : d;
287
+ })
288
+ .join(" per ");
289
+ return `${numerator} per ${denominator}`;
290
+ }
291
+
292
+ return getReadableUnitPart(unit);
169
293
  }
170
294
  }